/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.management.relation;

import static com.sun.jmx.defaults.JmxProperties.RELATION_LOGGER;
import static com.sun.jmx.mbeanserver.Util.cast;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;

import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.ReflectionException;

/**
 * The Relation Service is in charge of creating and deleting relation types
 * and relations, of handling the consistency and of providing query
 * mechanisms.
 * <P>It implements the NotificationBroadcaster by extending
 * NotificationBroadcasterSupport to send notifications when a relation is
 * removed from it.
 * <P>It implements the NotificationListener interface to be able to receive
 * notifications concerning unregistration of MBeans referenced in relation
 * roles and of relation MBeans.
 * <P>It implements the MBeanRegistration interface to be able to retrieve
 * its ObjectName and MBean Server.
 *
 * @since 1.5
 */
public class RelationService extends NotificationBroadcasterSupport
    implements RelationServiceMBean, MBeanRegistration, NotificationListener {

  //
  // Private members
  //

  // Map associating:
  //      <relation id> -> <RelationSupport object/ObjectName>
  // depending if the relation has been created using createRelation()
  // method (so internally handled) or is an MBean added as a relation by the
  // user
  private Map<String, Object> myRelId2ObjMap = new HashMap<String, Object>();

  // Map associating:
  //      <relation id> -> <relation type name>
  private Map<String, String> myRelId2RelTypeMap = new HashMap<String, String>();

  // Map associating:
  //      <relation MBean Object Name> -> <relation id>
  private Map<ObjectName, String> myRelMBeanObjName2RelIdMap =
      new HashMap<ObjectName, String>();

  // Map associating:
  //       <relation type name> -> <RelationType object>
  private Map<String, RelationType> myRelType2ObjMap =
      new HashMap<String, RelationType>();

  // Map associating:
  //       <relation type name> -> ArrayList of <relation id>
  // to list all the relations of a given type
  private Map<String, List<String>> myRelType2RelIdsMap =
      new HashMap<String, List<String>>();

  // Map associating:
  //       <ObjectName> -> HashMap
  // the value HashMap mapping:
  //       <relation id> -> ArrayList of <role name>
  // to track where a given MBean is referenced.
  private final Map<ObjectName, Map<String, List<String>>>
      myRefedMBeanObjName2RelIdsMap =
      new HashMap<ObjectName, Map<String, List<String>>>();

  // Flag to indicate if, when a notification is received for the
  // unregistration of an MBean referenced in a relation, if an immediate
  // "purge" of the relations (look for the relations no
  // longer valid) has to be performed , or if that will be performed only
  // when the purgeRelations method will be explicitly called.
  // true is immediate purge.
  private boolean myPurgeFlag = true;

  // Internal counter to provide sequence numbers for notifications sent by:
  // - the Relation Service
  // - a relation handled by the Relation Service
  private final AtomicLong atomicSeqNo = new AtomicLong();

  // ObjectName used to register the Relation Service in the MBean Server
  private ObjectName myObjName = null;

  // MBean Server where the Relation Service is registered
  private MBeanServer myMBeanServer = null;

  // Filter registered in the MBean Server with the Relation Service to be
  // informed of referenced MBean deregistrations
  private MBeanServerNotificationFilter myUnregNtfFilter = null;

  // List of unregistration notifications received (storage used if purge
  // of relations when unregistering a referenced MBean is not immediate but
  // on user request)
  private List<MBeanServerNotification> myUnregNtfList =
      new ArrayList<MBeanServerNotification>();

  //
  // Constructor
  //

  /**
   * Constructor.
   *
   * @param immediatePurgeFlag flag to indicate when a notification is received for the
   * unregistration of an MBean referenced in a relation, if an immediate "purge" of the relations
   * (look for the relations no longer valid) has to be performed , or if that will be performed
   * only when the purgeRelations method will be explicitly called. <P>true is immediate purge.
   */
  public RelationService(boolean immediatePurgeFlag) {

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "RelationService");

    setPurgeFlag(immediatePurgeFlag);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "RelationService");
    return;
  }

  /**
   * Checks if the Relation Service is active.
   * Current condition is that the Relation Service must be registered in the
   * MBean Server
   *
   * @throws RelationServiceNotRegisteredException if it is not registered
   */
  public void isActive()
      throws RelationServiceNotRegisteredException {
    if (myMBeanServer == null) {
      // MBean Server not set by preRegister(): relation service not
      // registered
      String excMsg =
          "Relation Service not registered in the MBean Server.";
      throw new RelationServiceNotRegisteredException(excMsg);
    }
    return;
  }

  //
  // MBeanRegistration interface
  //

  // Pre-registration: retrieves its ObjectName and MBean Server
  //
  // No exception thrown.
  public ObjectName preRegister(MBeanServer server,
      ObjectName name)
      throws Exception {

    myMBeanServer = server;
    myObjName = name;
    return name;
  }

  // Post-registration: does nothing
  public void postRegister(Boolean registrationDone) {
    return;
  }

  // Pre-unregistration: does nothing
  public void preDeregister()
      throws Exception {
    return;
  }

  // Post-unregistration: does nothing
  public void postDeregister() {
    return;
  }

  //
  // Accessors
  //

  /**
   * Returns the flag to indicate if when a notification is received for the
   * unregistration of an MBean referenced in a relation, if an immediate
   * "purge" of the relations (look for the relations no longer valid)
   * has to be performed , or if that will be performed only when the
   * purgeRelations method will be explicitly called.
   * <P>true is immediate purge.
   *
   * @return true if purges are automatic.
   * @see #setPurgeFlag
   */
  public boolean getPurgeFlag() {
    return myPurgeFlag;
  }

  /**
   * Sets the flag to indicate if when a notification is received for the
   * unregistration of an MBean referenced in a relation, if an immediate
   * "purge" of the relations (look for the relations no longer valid)
   * has to be performed , or if that will be performed only when the
   * purgeRelations method will be explicitly called.
   * <P>true is immediate purge.
   *
   * @param purgeFlag flag
   * @see #getPurgeFlag
   */
  public void setPurgeFlag(boolean purgeFlag) {

    myPurgeFlag = purgeFlag;
    return;
  }

  //
  // Relation type handling
  //

  /**
   * Creates a relation type (a RelationTypeSupport object) with given
   * role infos (provided by the RoleInfo objects), and adds it in the
   * Relation Service.
   *
   * @param relationTypeName name of the relation type
   * @param roleInfoArray array of role infos
   * @throws IllegalArgumentException if null parameter
   * @throws InvalidRelationTypeException If: <P>- there is already a relation type with that name
   * <P>- the same name has been used for two different role infos <P>- no role info provided <P>-
   * one null role info provided
   */
  public void createRelationType(String relationTypeName,
      RoleInfo[] roleInfoArray)
      throws IllegalArgumentException,
      InvalidRelationTypeException {

    if (relationTypeName == null || roleInfoArray == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "createRelationType", relationTypeName);

    // Can throw an InvalidRelationTypeException
    RelationType relType =
        new RelationTypeSupport(relationTypeName, roleInfoArray);

    addRelationTypeInt(relType);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "createRelationType");
    return;
  }

  /**
   * Adds given object as a relation type. The object is expected to
   * implement the RelationType interface.
   *
   * @param relationTypeObj relation type object (implementing the RelationType interface)
   * @throws IllegalArgumentException if null parameter or if {@link RelationType#getRelationTypeName
   * relationTypeObj.getRelationTypeName()} returns null.
   * @throws InvalidRelationTypeException if: <P>- the same name has been used for two different
   * roles <P>- no role info provided <P>- one null role info provided <P>- there is already a
   * relation type with that name
   */
  public void addRelationType(RelationType relationTypeObj)
      throws IllegalArgumentException,
      InvalidRelationTypeException {

    if (relationTypeObj == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "addRelationType");

    // Checks the role infos
    List<RoleInfo> roleInfoList = relationTypeObj.getRoleInfos();
    if (roleInfoList == null) {
      String excMsg = "No role info provided.";
      throw new InvalidRelationTypeException(excMsg);
    }

    RoleInfo[] roleInfoArray = new RoleInfo[roleInfoList.size()];
    int i = 0;
    for (RoleInfo currRoleInfo : roleInfoList) {
      roleInfoArray[i] = currRoleInfo;
      i++;
    }
    // Can throw InvalidRelationTypeException
    RelationTypeSupport.checkRoleInfos(roleInfoArray);

    addRelationTypeInt(relationTypeObj);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "addRelationType");
    return;
  }

  /**
   * Retrieves names of all known relation types.
   *
   * @return ArrayList of relation type names (Strings)
   */
  public List<String> getAllRelationTypeNames() {
    ArrayList<String> result;
    synchronized (myRelType2ObjMap) {
      result = new ArrayList<String>(myRelType2ObjMap.keySet());
    }
    return result;
  }

  /**
   * Retrieves list of role infos (RoleInfo objects) of a given relation
   * type.
   *
   * @param relationTypeName name of relation type
   * @return ArrayList of RoleInfo.
   * @throws IllegalArgumentException if null parameter
   * @throws RelationTypeNotFoundException if there is no relation type with that name.
   */
  public List<RoleInfo> getRoleInfos(String relationTypeName)
      throws IllegalArgumentException,
      RelationTypeNotFoundException {

    if (relationTypeName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getRoleInfos", relationTypeName);

    // Can throw a RelationTypeNotFoundException
    RelationType relType = getRelationType(relationTypeName);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "getRoleInfos");
    return relType.getRoleInfos();
  }

  /**
   * Retrieves role info for given role name of a given relation type.
   *
   * @param relationTypeName name of relation type
   * @param roleInfoName name of role
   * @return RoleInfo object.
   * @throws IllegalArgumentException if null parameter
   * @throws RelationTypeNotFoundException if the relation type is not known in the Relation
   * Service
   * @throws RoleInfoNotFoundException if the role is not part of the relation type.
   */
  public RoleInfo getRoleInfo(String relationTypeName,
      String roleInfoName)
      throws IllegalArgumentException,
      RelationTypeNotFoundException,
      RoleInfoNotFoundException {

    if (relationTypeName == null || roleInfoName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getRoleInfo", new Object[]{relationTypeName, roleInfoName});

    // Can throw a RelationTypeNotFoundException
    RelationType relType = getRelationType(relationTypeName);

    // Can throw a RoleInfoNotFoundException
    RoleInfo roleInfo = relType.getRoleInfo(roleInfoName);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "getRoleInfo");
    return roleInfo;
  }

  /**
   * Removes given relation type from Relation Service.
   * <P>The relation objects of that type will be removed from the
   * Relation Service.
   *
   * @param relationTypeName name of the relation type to be removed
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server
   * @throws IllegalArgumentException if null parameter
   * @throws RelationTypeNotFoundException If there is no relation type with that name
   */
  public void removeRelationType(String relationTypeName)
      throws RelationServiceNotRegisteredException,
      IllegalArgumentException,
      RelationTypeNotFoundException {

    // Can throw RelationServiceNotRegisteredException
    isActive();

    if (relationTypeName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "removeRelationType", relationTypeName);

    // Checks if the relation type to be removed exists
    // Can throw a RelationTypeNotFoundException
    RelationType relType = getRelationType(relationTypeName);

    // Retrieves the relation ids for relations of that type
    List<String> relIdList = null;
    synchronized (myRelType2RelIdsMap) {
      // Note: take a copy of the list as it is a part of a map that
      //       will be updated by removeRelation() below.
      List<String> relIdList1 =
          myRelType2RelIdsMap.get(relationTypeName);
      if (relIdList1 != null) {
        relIdList = new ArrayList<String>(relIdList1);
      }
    }

    // Removes the relation type from all maps
    synchronized (myRelType2ObjMap) {
      myRelType2ObjMap.remove(relationTypeName);
    }
    synchronized (myRelType2RelIdsMap) {
      myRelType2RelIdsMap.remove(relationTypeName);
    }

    // Removes all relations of that type
    if (relIdList != null) {
      for (String currRelId : relIdList) {
        // Note: will remove it from myRelId2RelTypeMap :)
        //
        // Can throw RelationServiceNotRegisteredException (detected
        // above)
        // Shall not throw a RelationNotFoundException
        try {
          removeRelation(currRelId);
        } catch (RelationNotFoundException exc1) {
          throw new RuntimeException(exc1.getMessage());
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "removeRelationType");
    return;
  }

  //
  // Relation handling
  //

  /**
   * Creates a simple relation (represented by a RelationSupport object) of
   * given relation type, and adds it in the Relation Service.
   * <P>Roles are initialized according to the role list provided in
   * parameter. The ones not initialized in this way are set to an empty
   * ArrayList of ObjectNames.
   * <P>A RelationNotification, with type RELATION_BASIC_CREATION, is sent.
   *
   * @param relationId relation identifier, to identify uniquely the relation inside the Relation
   * Service
   * @param relationTypeName name of the relation type (has to be created in the Relation Service)
   * @param roleList role list to initialize roles of the relation (can be null).
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server
   * @throws IllegalArgumentException if null parameter, except the role list which can be null if
   * no role initialization
   * @throws RoleNotFoundException if a value is provided for a role that does not exist in the
   * relation type
   * @throws InvalidRelationIdException if relation id already used
   * @throws RelationTypeNotFoundException if relation type not known in Relation Service
   * @throws InvalidRoleValueException if: <P>- the same role name is used for two different roles
   * <P>- the number of referenced MBeans in given value is less than expected minimum degree <P>-
   * the number of referenced MBeans in provided value exceeds expected maximum degree <P>- one
   * referenced MBean in the value is not an Object of the MBean class expected for that role <P>-
   * an MBean provided for that role does not exist
   */
  public void createRelation(String relationId,
      String relationTypeName,
      RoleList roleList)
      throws RelationServiceNotRegisteredException,
      IllegalArgumentException,
      RoleNotFoundException,
      InvalidRelationIdException,
      RelationTypeNotFoundException,
      InvalidRoleValueException {

    // Can throw RelationServiceNotRegisteredException
    isActive();

    if (relationId == null ||
        relationTypeName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "createRelation",
        new Object[]{relationId, relationTypeName, roleList});

    // Creates RelationSupport object
    // Can throw InvalidRoleValueException
    RelationSupport relObj = new RelationSupport(relationId,
        myObjName,
        relationTypeName,
        roleList);

    // Adds relation object as a relation into the Relation Service
    // Can throw RoleNotFoundException, InvalidRelationId,
    // RelationTypeNotFoundException, InvalidRoleValueException
    //
    // Cannot throw MBeanException
    addRelationInt(true,
        relObj,
        null,
        relationId,
        relationTypeName,
        roleList);
    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "createRelation");
    return;
  }

  /**
   * Adds an MBean created by the user (and registered by him in the MBean
   * Server) as a relation in the Relation Service.
   * <P>To be added as a relation, the MBean must conform to the
   * following:
   * <P>- implement the Relation interface
   * <P>- have for RelationService ObjectName the ObjectName of current
   * Relation Service
   * <P>- have a relation id unique and unused in current Relation Service
   * <P>- have for relation type a relation type created in the Relation
   * Service
   * <P>- have roles conforming to the role info provided in the relation
   * type.
   *
   * @param relationObjectName ObjectName of the relation MBean to be added.
   * @throws IllegalArgumentException if null parameter
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server
   * @throws NoSuchMethodException If the MBean does not implement the Relation interface
   * @throws InvalidRelationIdException if: <P>- no relation identifier in MBean <P>- the relation
   * identifier is already used in the Relation Service
   * @throws InstanceNotFoundException if the MBean for given ObjectName has not been registered
   * @throws InvalidRelationServiceException if: <P>- no Relation Service name in MBean <P>- the
   * Relation Service name in the MBean is not the one of the current Relation Service
   * @throws RelationTypeNotFoundException if: <P>- no relation type name in MBean <P>- the relation
   * type name in MBean does not correspond to a relation type created in the Relation Service
   * @throws InvalidRoleValueException if: <P>- the number of referenced MBeans in a role is less
   * than expected minimum degree <P>- the number of referenced MBeans in a role exceeds expected
   * maximum degree <P>- one referenced MBean in the value is not an Object of the MBean class
   * expected for that role <P>- an MBean provided for a role does not exist
   * @throws RoleNotFoundException if a value is provided for a role that does not exist in the
   * relation type
   */
  public void addRelation(ObjectName relationObjectName)
      throws IllegalArgumentException,
      RelationServiceNotRegisteredException,
      NoSuchMethodException,
      InvalidRelationIdException,
      InstanceNotFoundException,
      InvalidRelationServiceException,
      RelationTypeNotFoundException,
      RoleNotFoundException,
      InvalidRoleValueException {

    if (relationObjectName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "addRelation", relationObjectName);

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // Checks that the relation MBean implements the Relation interface.
    // It will also check that the provided ObjectName corresponds to a
    // registered MBean (else will throw an InstanceNotFoundException)
    if ((!(myMBeanServer.isInstanceOf(relationObjectName, "javax.management.relation.Relation")))) {
      String excMsg = "This MBean does not implement the Relation interface.";
      throw new NoSuchMethodException(excMsg);
    }
    // Checks there is a relation id in the relation MBean (its uniqueness
    // is checked in addRelationInt())
    // Can throw InstanceNotFoundException (but detected above)
    // No MBeanException as no exception raised by this method, and no
    // ReflectionException
    String relId;
    try {
      relId = (String) (myMBeanServer.getAttribute(relationObjectName,
          "RelationId"));

    } catch (MBeanException exc1) {
      throw new RuntimeException(
          (exc1.getTargetException()).getMessage());
    } catch (ReflectionException exc2) {
      throw new RuntimeException(exc2.getMessage());
    } catch (AttributeNotFoundException exc3) {
      throw new RuntimeException(exc3.getMessage());
    }

    if (relId == null) {
      String excMsg = "This MBean does not provide a relation id.";
      throw new InvalidRelationIdException(excMsg);
    }
    // Checks that the Relation Service where the relation MBean is
    // expected to be added is the current one
    // Can throw InstanceNotFoundException (but detected above)
    // No MBeanException as no exception raised by this method, no
    // ReflectionException
    ObjectName relServObjName;
    try {
      relServObjName = (ObjectName)
          (myMBeanServer.getAttribute(relationObjectName,
              "RelationServiceName"));

    } catch (MBeanException exc1) {
      throw new RuntimeException(
          (exc1.getTargetException()).getMessage());
    } catch (ReflectionException exc2) {
      throw new RuntimeException(exc2.getMessage());
    } catch (AttributeNotFoundException exc3) {
      throw new RuntimeException(exc3.getMessage());
    }

    boolean badRelServFlag = false;
    if (relServObjName == null) {
      badRelServFlag = true;

    } else if (!(relServObjName.equals(myObjName))) {
      badRelServFlag = true;
    }
    if (badRelServFlag) {
      String excMsg = "The Relation Service referenced in the MBean is not the current one.";
      throw new InvalidRelationServiceException(excMsg);
    }
    // Checks that a relation type has been specified for the relation
    // Can throw InstanceNotFoundException (but detected above)
    // No MBeanException as no exception raised by this method, no
    // ReflectionException
    String relTypeName;
    try {
      relTypeName = (String) (myMBeanServer.getAttribute(relationObjectName,
          "RelationTypeName"));

    } catch (MBeanException exc1) {
      throw new RuntimeException(
          (exc1.getTargetException()).getMessage());
    } catch (ReflectionException exc2) {
      throw new RuntimeException(exc2.getMessage());
    } catch (AttributeNotFoundException exc3) {
      throw new RuntimeException(exc3.getMessage());
    }
    if (relTypeName == null) {
      String excMsg = "No relation type provided.";
      throw new RelationTypeNotFoundException(excMsg);
    }
    // Retrieves all roles without considering read mode
    // Can throw InstanceNotFoundException (but detected above)
    // No MBeanException as no exception raised by this method, no
    // ReflectionException
    RoleList roleList;
    try {
      roleList = (RoleList) (myMBeanServer.invoke(relationObjectName,
          "retrieveAllRoles",
          null,
          null));
    } catch (MBeanException exc1) {
      throw new RuntimeException(
          (exc1.getTargetException()).getMessage());
    } catch (ReflectionException exc2) {
      throw new RuntimeException(exc2.getMessage());
    }

    // Can throw RoleNotFoundException, InvalidRelationIdException,
    // RelationTypeNotFoundException, InvalidRoleValueException
    addRelationInt(false,
        null,
        relationObjectName,
        relId,
        relTypeName,
        roleList);
    // Adds relation MBean ObjectName in map
    synchronized (myRelMBeanObjName2RelIdMap) {
      myRelMBeanObjName2RelIdMap.put(relationObjectName, relId);
    }

    // Updates flag to specify that the relation is managed by the Relation
    // Service
    // This flag and setter are inherited from RelationSupport and not parts
    // of the Relation interface, so may be not supported.
    try {
      myMBeanServer.setAttribute(relationObjectName,
          new Attribute(
              "RelationServiceManagementFlag",
              Boolean.TRUE));
    } catch (Exception exc) {
      // OK : The flag is not supported.
    }

    // Updates listener information to received notification for
    // unregistration of this MBean
    List<ObjectName> newRefList = new ArrayList<ObjectName>();
    newRefList.add(relationObjectName);
    updateUnregistrationListener(newRefList, null);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "addRelation");
    return;
  }

  /**
   * If the relation is represented by an MBean (created by the user and
   * added as a relation in the Relation Service), returns the ObjectName of
   * the MBean.
   *
   * @param relationId relation id identifying the relation
   * @return ObjectName of the corresponding relation MBean, or null if the relation is not an
   * MBean.
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException there is no relation associated to that id
   */
  public ObjectName isRelationMBean(String relationId)
      throws IllegalArgumentException,
      RelationNotFoundException {

    if (relationId == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "isRelationMBean", relationId);

    // Can throw RelationNotFoundException
    Object result = getRelation(relationId);
    if (result instanceof ObjectName) {
      return ((ObjectName) result);
    } else {
      return null;
    }
  }

  /**
   * Returns the relation id associated to the given ObjectName if the
   * MBean has been added as a relation in the Relation Service.
   *
   * @param objectName ObjectName of supposed relation
   * @return relation id (String) or null (if the ObjectName is not a relation handled by the
   * Relation Service)
   * @throws IllegalArgumentException if null parameter
   */
  public String isRelation(ObjectName objectName)
      throws IllegalArgumentException {

    if (objectName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "isRelation", objectName);

    String result = null;
    synchronized (myRelMBeanObjName2RelIdMap) {
      String relId = myRelMBeanObjName2RelIdMap.get(objectName);
      if (relId != null) {
        result = relId;
      }
    }
    return result;
  }

  /**
   * Checks if there is a relation identified in Relation Service with given
   * relation id.
   *
   * @param relationId relation id identifying the relation
   * @return boolean: true if there is a relation, false else
   * @throws IllegalArgumentException if null parameter
   */
  public Boolean hasRelation(String relationId)
      throws IllegalArgumentException {

    if (relationId == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "hasRelation", relationId);

    try {
      // Can throw RelationNotFoundException
      Object result = getRelation(relationId);
      return true;
    } catch (RelationNotFoundException exc) {
      return false;
    }
  }

  /**
   * Returns all the relation ids for all the relations handled by the
   * Relation Service.
   *
   * @return ArrayList of String
   */
  public List<String> getAllRelationIds() {
    List<String> result;
    synchronized (myRelId2ObjMap) {
      result = new ArrayList<String>(myRelId2ObjMap.keySet());
    }
    return result;
  }

  /**
   * Checks if given Role can be read in a relation of the given type.
   *
   * @param roleName name of role to be checked
   * @param relationTypeName name of the relation type
   * @return an Integer wrapping an integer corresponding to possible problems represented as
   * constants in RoleUnresolved: <P>- 0 if role can be read <P>- integer corresponding to
   * RoleStatus.NO_ROLE_WITH_NAME <P>- integer corresponding to RoleStatus.ROLE_NOT_READABLE
   * @throws IllegalArgumentException if null parameter
   * @throws RelationTypeNotFoundException if the relation type is not known in the Relation
   * Service
   */
  public Integer checkRoleReading(String roleName,
      String relationTypeName)
      throws IllegalArgumentException,
      RelationTypeNotFoundException {

    if (roleName == null || relationTypeName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "checkRoleReading", new Object[]{roleName, relationTypeName});

    Integer result;

    // Can throw a RelationTypeNotFoundException
    RelationType relType = getRelationType(relationTypeName);

    try {
      // Can throw a RoleInfoNotFoundException to be transformed into
      // returned value RoleStatus.NO_ROLE_WITH_NAME
      RoleInfo roleInfo = relType.getRoleInfo(roleName);

      result = checkRoleInt(1,
          roleName,
          null,
          roleInfo,
          false);

    } catch (RoleInfoNotFoundException exc) {
      result = Integer.valueOf(RoleStatus.NO_ROLE_WITH_NAME);
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "checkRoleReading");
    return result;
  }

  /**
   * Checks if given Role can be set in a relation of given type.
   *
   * @param role role to be checked
   * @param relationTypeName name of relation type
   * @param initFlag flag to specify that the checking is done for the initialization of a role,
   * write access shall not be verified.
   * @return an Integer wrapping an integer corresponding to possible problems represented as
   * constants in RoleUnresolved: <P>- 0 if role can be set <P>- integer corresponding to
   * RoleStatus.NO_ROLE_WITH_NAME <P>- integer for RoleStatus.ROLE_NOT_WRITABLE <P>- integer for
   * RoleStatus.LESS_THAN_MIN_ROLE_DEGREE <P>- integer for RoleStatus.MORE_THAN_MAX_ROLE_DEGREE <P>-
   * integer for RoleStatus.REF_MBEAN_OF_INCORRECT_CLASS <P>- integer for
   * RoleStatus.REF_MBEAN_NOT_REGISTERED
   * @throws IllegalArgumentException if null parameter
   * @throws RelationTypeNotFoundException if unknown relation type
   */
  public Integer checkRoleWriting(Role role,
      String relationTypeName,
      Boolean initFlag)
      throws IllegalArgumentException,
      RelationTypeNotFoundException {

    if (role == null ||
        relationTypeName == null ||
        initFlag == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "checkRoleWriting",
        new Object[]{role, relationTypeName, initFlag});

    // Can throw a RelationTypeNotFoundException
    RelationType relType = getRelationType(relationTypeName);

    String roleName = role.getRoleName();
    List<ObjectName> roleValue = role.getRoleValue();
    boolean writeChkFlag = true;
    if (initFlag.booleanValue()) {
      writeChkFlag = false;
    }

    RoleInfo roleInfo;
    try {
      roleInfo = relType.getRoleInfo(roleName);
    } catch (RoleInfoNotFoundException exc) {
      RELATION_LOGGER.exiting(RelationService.class.getName(),
          "checkRoleWriting");
      return Integer.valueOf(RoleStatus.NO_ROLE_WITH_NAME);
    }

    Integer result = checkRoleInt(2,
        roleName,
        roleValue,
        roleInfo,
        writeChkFlag);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "checkRoleWriting");
    return result;
  }

  /**
   * Sends a notification (RelationNotification) for a relation creation.
   * The notification type is:
   * <P>- RelationNotification.RELATION_BASIC_CREATION if the relation is an
   * object internal to the Relation Service
   * <P>- RelationNotification.RELATION_MBEAN_CREATION if the relation is a
   * MBean added as a relation.
   * <P>The source object is the Relation Service itself.
   * <P>It is called in Relation Service createRelation() and
   * addRelation() methods.
   *
   * @param relationId relation identifier of the updated relation
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if there is no relation for given relation id
   */
  public void sendRelationCreationNotification(String relationId)
      throws IllegalArgumentException,
      RelationNotFoundException {

    if (relationId == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "sendRelationCreationNotification", relationId);

    // Message
    StringBuilder ntfMsg = new StringBuilder("Creation of relation ");
    ntfMsg.append(relationId);

    // Can throw RelationNotFoundException
    sendNotificationInt(1,
        ntfMsg.toString(),
        relationId,
        null,
        null,
        null,
        null);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "sendRelationCreationNotification");
    return;
  }

  /**
   * Sends a notification (RelationNotification) for a role update in the
   * given relation. The notification type is:
   * <P>- RelationNotification.RELATION_BASIC_UPDATE if the relation is an
   * object internal to the Relation Service
   * <P>- RelationNotification.RELATION_MBEAN_UPDATE if the relation is a
   * MBean added as a relation.
   * <P>The source object is the Relation Service itself.
   * <P>It is called in relation MBean setRole() (for given role) and
   * setRoles() (for each role) methods (implementation provided in
   * RelationSupport class).
   * <P>It is also called in Relation Service setRole() (for given role) and
   * setRoles() (for each role) methods.
   *
   * @param relationId relation identifier of the updated relation
   * @param newRole new role (name and new value)
   * @param oldValue old role value (List of ObjectName objects)
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if there is no relation for given relation id
   */
  public void sendRoleUpdateNotification(String relationId,
      Role newRole,
      List<ObjectName> oldValue)
      throws IllegalArgumentException,
      RelationNotFoundException {

    if (relationId == null ||
        newRole == null ||
        oldValue == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    if (!(oldValue instanceof ArrayList<?>)) {
      oldValue = new ArrayList<ObjectName>(oldValue);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "sendRoleUpdateNotification",
        new Object[]{relationId, newRole, oldValue});

    String roleName = newRole.getRoleName();
    List<ObjectName> newRoleVal = newRole.getRoleValue();

    // Message
    String newRoleValString = Role.roleValueToString(newRoleVal);
    String oldRoleValString = Role.roleValueToString(oldValue);
    StringBuilder ntfMsg = new StringBuilder("Value of role ");
    ntfMsg.append(roleName);
    ntfMsg.append(" has changed\nOld value:\n");
    ntfMsg.append(oldRoleValString);
    ntfMsg.append("\nNew value:\n");
    ntfMsg.append(newRoleValString);

    // Can throw a RelationNotFoundException
    sendNotificationInt(2,
        ntfMsg.toString(),
        relationId,
        null,
        roleName,
        newRoleVal,
        oldValue);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "sendRoleUpdateNotification");
  }

  /**
   * Sends a notification (RelationNotification) for a relation removal.
   * The notification type is:
   * <P>- RelationNotification.RELATION_BASIC_REMOVAL if the relation is an
   * object internal to the Relation Service
   * <P>- RelationNotification.RELATION_MBEAN_REMOVAL if the relation is a
   * MBean added as a relation.
   * <P>The source object is the Relation Service itself.
   * <P>It is called in Relation Service removeRelation() method.
   *
   * @param relationId relation identifier of the updated relation
   * @param unregMBeanList List of ObjectNames of MBeans expected to be unregistered due to relation
   * removal (can be null)
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if there is no relation for given relation id
   */
  public void sendRelationRemovalNotification(String relationId,
      List<ObjectName> unregMBeanList)
      throws IllegalArgumentException,
      RelationNotFoundException {

    if (relationId == null) {
      String excMsg = "Invalid parameter";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "sendRelationRemovalNotification",
        new Object[]{relationId, unregMBeanList});

    // Can throw RelationNotFoundException
    sendNotificationInt(3,
        "Removal of relation " + relationId,
        relationId,
        unregMBeanList,
        null,
        null,
        null);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "sendRelationRemovalNotification");
    return;
  }

  /**
   * Handles update of the Relation Service role map for the update of given
   * role in given relation.
   * <P>It is called in relation MBean setRole() (for given role) and
   * setRoles() (for each role) methods (implementation provided in
   * RelationSupport class).
   * <P>It is also called in Relation Service setRole() (for given role) and
   * setRoles() (for each role) methods.
   * <P>To allow the Relation Service to maintain the consistency (in case
   * of MBean unregistration) and to be able to perform queries, this method
   * must be called when a role is updated.
   *
   * @param relationId relation identifier of the updated relation
   * @param newRole new role (name and new value)
   * @param oldValue old role value (List of ObjectName objects)
   * @throws IllegalArgumentException if null parameter
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server
   * @throws RelationNotFoundException if no relation for given id.
   */
  public void updateRoleMap(String relationId,
      Role newRole,
      List<ObjectName> oldValue)
      throws IllegalArgumentException,
      RelationServiceNotRegisteredException,
      RelationNotFoundException {

    if (relationId == null ||
        newRole == null ||
        oldValue == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "updateRoleMap", new Object[]{relationId, newRole, oldValue});

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // Verifies the relation has been added in the Relation Service
    // Can throw a RelationNotFoundException
    Object result = getRelation(relationId);

    String roleName = newRole.getRoleName();
    List<ObjectName> newRoleValue = newRole.getRoleValue();
    // Note: no need to test if oldValue not null before cloning,
    //       tested above.
    List<ObjectName> oldRoleValue =
        new ArrayList<ObjectName>(oldValue);

    // List of ObjectNames of new referenced MBeans
    List<ObjectName> newRefList = new ArrayList<ObjectName>();

    for (ObjectName currObjName : newRoleValue) {

      // Checks if this ObjectName was already present in old value
      // Note: use copy (oldRoleValue) instead of original
      //       oldValue to speed up, as oldRoleValue is decreased
      //       by removing unchanged references :)
      int currObjNamePos = oldRoleValue.indexOf(currObjName);

      if (currObjNamePos == -1) {
        // New reference to an ObjectName

        // Stores this reference into map
        // Returns true if new reference, false if MBean already
        // referenced
        boolean isNewFlag = addNewMBeanReference(currObjName,
            relationId,
            roleName);

        if (isNewFlag) {
          // Adds it into list of new reference
          newRefList.add(currObjName);
        }

      } else {
        // MBean was already referenced in old value

        // Removes it from old value (local list) to ignore it when
        // looking for remove MBean references
        oldRoleValue.remove(currObjNamePos);
      }
    }

    // List of ObjectNames of MBeans no longer referenced
    List<ObjectName> obsRefList = new ArrayList<ObjectName>();

    // Each ObjectName remaining in oldRoleValue is an ObjectName no longer
    // referenced in new value
    for (ObjectName currObjName : oldRoleValue) {
      // Removes MBean reference from map
      // Returns true if the MBean is no longer referenced in any
      // relation
      boolean noLongerRefFlag = removeMBeanReference(currObjName,
          relationId,
          roleName,
          false);

      if (noLongerRefFlag) {
        // Adds it into list of references to be removed
        obsRefList.add(currObjName);
      }
    }

    // To avoid having one listener per ObjectName of referenced MBean,
    // and to increase performances, there is only one listener recording
    // all ObjectNames of interest
    updateUnregistrationListener(newRefList, obsRefList);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "updateRoleMap");
    return;
  }

  /**
   * Removes given relation from the Relation Service.
   * <P>A RelationNotification notification is sent, its type being:
   * <P>- RelationNotification.RELATION_BASIC_REMOVAL if the relation was
   * only internal to the Relation Service
   * <P>- RelationNotification.RELATION_MBEAN_REMOVAL if the relation is
   * registered as an MBean.
   * <P>For MBeans referenced in such relation, nothing will be done,
   *
   * @param relationId relation id of the relation to be removed
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if no relation corresponding to given relation id
   */
  public void removeRelation(String relationId)
      throws RelationServiceNotRegisteredException,
      IllegalArgumentException,
      RelationNotFoundException {

    // Can throw RelationServiceNotRegisteredException
    isActive();

    if (relationId == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "removeRelation", relationId);

    // Checks there is a relation with this id
    // Can throw RelationNotFoundException
    Object result = getRelation(relationId);

    // Removes it from listener filter
    if (result instanceof ObjectName) {
      List<ObjectName> obsRefList = new ArrayList<ObjectName>();
      obsRefList.add((ObjectName) result);
      // Can throw a RelationServiceNotRegisteredException
      updateUnregistrationListener(null, obsRefList);
    }

    // Sends a notification
    // Note: has to be done FIRST as needs the relation to be still in the
    //       Relation Service
    // No RelationNotFoundException as checked above

    // Revisit [cebro] Handle CIM "Delete" and "IfDeleted" qualifiers:
    //   deleting the relation can mean to delete referenced MBeans. In
    //   that case, MBeans to be unregistered are put in a list sent along
    //   with the notification below

    // Can throw a RelationNotFoundException (but detected above)
    sendRelationRemovalNotification(relationId, null);

    // Removes the relation from various internal maps

    //  - MBean reference map
    // Retrieves the MBeans referenced in this relation
    // Note: here we cannot use removeMBeanReference() because it would
    //       require to know the MBeans referenced in the relation. For
    //       that it would be necessary to call 'getReferencedMBeans()'
    //       on the relation itself. Ok if it is an internal one, but if
    //       it is an MBean, it is possible it is already unregistered, so
    //       not available through the MBean Server.
    List<ObjectName> refMBeanList = new ArrayList<ObjectName>();
    // List of MBeans no longer referenced in any relation, to be
    // removed fom the map
    List<ObjectName> nonRefObjNameList = new ArrayList<ObjectName>();

    synchronized (myRefedMBeanObjName2RelIdsMap) {

      for (ObjectName currRefObjName :
          myRefedMBeanObjName2RelIdsMap.keySet()) {

        // Retrieves relations where the MBean is referenced
        Map<String, List<String>> relIdMap =
            myRefedMBeanObjName2RelIdsMap.get(currRefObjName);

        if (relIdMap.containsKey(relationId)) {
          relIdMap.remove(relationId);
          refMBeanList.add(currRefObjName);
        }

        if (relIdMap.isEmpty()) {
          // MBean no longer referenced
          // Note: do not remove it here because pointed by the
          //       iterator!
          nonRefObjNameList.add(currRefObjName);
        }
      }

      // Cleans MBean reference map by removing MBeans no longer
      // referenced
      for (ObjectName currRefObjName : nonRefObjNameList) {
        myRefedMBeanObjName2RelIdsMap.remove(currRefObjName);
      }
    }

    // - Relation id to object map
    synchronized (myRelId2ObjMap) {
      myRelId2ObjMap.remove(relationId);
    }

    if (result instanceof ObjectName) {
      // - ObjectName to relation id map
      synchronized (myRelMBeanObjName2RelIdMap) {
        myRelMBeanObjName2RelIdMap.remove((ObjectName) result);
      }
    }

    // Relation id to relation type name map
    // First retrieves the relation type name
    String relTypeName;
    synchronized (myRelId2RelTypeMap) {
      relTypeName = myRelId2RelTypeMap.get(relationId);
      myRelId2RelTypeMap.remove(relationId);
    }
    // - Relation type name to relation id map
    synchronized (myRelType2RelIdsMap) {
      List<String> relIdList = myRelType2RelIdsMap.get(relTypeName);
      if (relIdList != null) {
        // Can be null if called from removeRelationType()
        relIdList.remove(relationId);
        if (relIdList.isEmpty()) {
          // No other relation of that type
          myRelType2RelIdsMap.remove(relTypeName);
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "removeRelation");
    return;
  }

  /**
   * Purges the relations.
   *
   * <P>Depending on the purgeFlag value, this method is either called
   * automatically when a notification is received for the unregistration of
   * an MBean referenced in a relation (if the flag is set to true), or not
   * (if the flag is set to false).
   * <P>In that case it is up to the user to call it to maintain the
   * consistency of the relations. To be kept in mind that if an MBean is
   * unregistered and the purge not done immediately, if the ObjectName is
   * reused and assigned to another MBean referenced in a relation, calling
   * manually this purgeRelations() method will cause trouble, as will
   * consider the ObjectName as corresponding to the unregistered MBean, not
   * seeing the new one.
   *
   * <P>The behavior depends on the cardinality of the role where the
   * unregistered MBean is referenced:
   * <P>- if removing one MBean reference in the role makes its number of
   * references less than the minimum degree, the relation has to be removed.
   * <P>- if the remaining number of references after removing the MBean
   * reference is still in the cardinality range, keep the relation and
   * update it calling its handleMBeanUnregistration() callback.
   *
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server.
   */
  public void purgeRelations()
      throws RelationServiceNotRegisteredException {

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "purgeRelations");

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // Revisit [cebro] Handle the CIM "Delete" and "IfDeleted" qualifier:
    //    if the unregistered MBean has the "IfDeleted" qualifier,
    //    possible that the relation itself or other referenced MBeans
    //    have to be removed (then a notification would have to be sent
    //    to inform that they should be unregistered.

    // Clones the list of notifications to be able to still receive new
    // notifications while proceeding those ones
    List<MBeanServerNotification> localUnregNtfList;
    synchronized (myRefedMBeanObjName2RelIdsMap) {
      localUnregNtfList =
          new ArrayList<MBeanServerNotification>(myUnregNtfList);
      // Resets list
      myUnregNtfList = new ArrayList<MBeanServerNotification>();
    }

    // Updates the listener filter to avoid receiving notifications for
    // those MBeans again
    // Makes also a local "myRefedMBeanObjName2RelIdsMap" map, mapping
    // ObjectName -> relId -> roles, to remove the MBean from the global
    // map
    // List of references to be removed from the listener filter
    List<ObjectName> obsRefList = new ArrayList<ObjectName>();
    // Map including ObjectNames for unregistered MBeans, with
    // referencing relation ids and roles
    Map<ObjectName, Map<String, List<String>>> localMBean2RelIdMap =
        new HashMap<ObjectName, Map<String, List<String>>>();

    synchronized (myRefedMBeanObjName2RelIdsMap) {
      for (MBeanServerNotification currNtf : localUnregNtfList) {

        ObjectName unregMBeanName = currNtf.getMBeanName();

        // Adds the unregsitered MBean in the list of references to
        // remove from the listener filter
        obsRefList.add(unregMBeanName);

        // Retrieves the associated map of relation ids and roles
        Map<String, List<String>> relIdMap =
            myRefedMBeanObjName2RelIdsMap.get(unregMBeanName);
        localMBean2RelIdMap.put(unregMBeanName, relIdMap);

        myRefedMBeanObjName2RelIdsMap.remove(unregMBeanName);
      }
    }

    // Updates the listener
    // Can throw RelationServiceNotRegisteredException
    updateUnregistrationListener(null, obsRefList);

    for (MBeanServerNotification currNtf : localUnregNtfList) {

      ObjectName unregMBeanName = currNtf.getMBeanName();

      // Retrieves the relations where the MBean is referenced
      Map<String, List<String>> localRelIdMap =
          localMBean2RelIdMap.get(unregMBeanName);

      // List of relation ids where the unregistered MBean is
      // referenced
      for (Map.Entry<String, List<String>> currRel :
          localRelIdMap.entrySet()) {
        final String currRelId = currRel.getKey();
        // List of roles of the relation where the MBean is
        // referenced
        List<String> localRoleNameList = currRel.getValue();

        // Checks if the relation has to be removed or not,
        // regarding expected minimum role cardinality and current
        // number of references after removal of the current one
        // If the relation is kept, calls
        // handleMBeanUnregistration() callback of the relation to
        // update it
        //
        // Can throw RelationServiceNotRegisteredException
        //
        // Shall not throw RelationNotFoundException,
        // RoleNotFoundException, MBeanException
        try {
          handleReferenceUnregistration(currRelId,
              unregMBeanName,
              localRoleNameList);
        } catch (RelationNotFoundException exc1) {
          throw new RuntimeException(exc1.getMessage());
        } catch (RoleNotFoundException exc2) {
          throw new RuntimeException(exc2.getMessage());
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "purgeRelations");
    return;
  }

  /**
   * Retrieves the relations where a given MBean is referenced.
   * <P>This corresponds to the CIM "References" and "ReferenceNames"
   * operations.
   *
   * @param mbeanName ObjectName of MBean
   * @param relationTypeName can be null; if specified, only the relations of that type will be
   * considered in the search. Else all relation types are considered.
   * @param roleName can be null; if specified, only the relations where the MBean is referenced in
   * that role will be returned. Else all roles are considered.
   * @return an HashMap, where the keys are the relation ids of the relations where the MBean is
   * referenced, and the value is, for each key, an ArrayList of role names (as an MBean can be
   * referenced in several roles in the same relation).
   * @throws IllegalArgumentException if null parameter
   */
  public Map<String, List<String>>
  findReferencingRelations(ObjectName mbeanName,
      String relationTypeName,
      String roleName)
      throws IllegalArgumentException {

    if (mbeanName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "findReferencingRelations",
        new Object[]{mbeanName, relationTypeName, roleName});

    Map<String, List<String>> result = new HashMap<String, List<String>>();

    synchronized (myRefedMBeanObjName2RelIdsMap) {

      // Retrieves the relations referencing the MBean
      Map<String, List<String>> relId2RoleNamesMap =
          myRefedMBeanObjName2RelIdsMap.get(mbeanName);

      if (relId2RoleNamesMap != null) {

        // Relation Ids where the MBean is referenced
        Set<String> allRelIdSet = relId2RoleNamesMap.keySet();

        // List of relation ids of interest regarding the selected
        // relation type
        List<String> relIdList;
        if (relationTypeName == null) {
          // Considers all relations
          relIdList = new ArrayList<String>(allRelIdSet);

        } else {

          relIdList = new ArrayList<String>();

          // Considers only the relation ids for relations of given
          // type
          for (String currRelId : allRelIdSet) {

            // Retrieves its relation type
            String currRelTypeName;
            synchronized (myRelId2RelTypeMap) {
              currRelTypeName =
                  myRelId2RelTypeMap.get(currRelId);
            }

            if (currRelTypeName.equals(relationTypeName)) {

              relIdList.add(currRelId);

            }
          }
        }

        // Now looks at the roles where the MBean is expected to be
        // referenced

        for (String currRelId : relIdList) {
          // Retrieves list of role names where the MBean is
          // referenced
          List<String> currRoleNameList =
              relId2RoleNamesMap.get(currRelId);

          if (roleName == null) {
            // All roles to be considered
            // Note: no need to test if list not null before
            //       cloning, MUST be not null else bug :(
            result.put(currRelId,
                new ArrayList<String>(currRoleNameList));

          } else if (currRoleNameList.contains(roleName)) {
            // Filters only the relations where the MBean is
            // referenced in // given role
            List<String> dummyList = new ArrayList<String>();
            dummyList.add(roleName);
            result.put(currRelId, dummyList);
          }
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "findReferencingRelations");
    return result;
  }

  /**
   * Retrieves the MBeans associated to given one in a relation.
   * <P>This corresponds to CIM Associators and AssociatorNames operations.
   *
   * @param mbeanName ObjectName of MBean
   * @param relationTypeName can be null; if specified, only the relations of that type will be
   * considered in the search. Else all relation types are considered.
   * @param roleName can be null; if specified, only the relations where the MBean is referenced in
   * that role will be considered. Else all roles are considered.
   * @return an HashMap, where the keys are the ObjectNames of the MBeans associated to given MBean,
   * and the value is, for each key, an ArrayList of the relation ids of the relations where the key
   * MBean is associated to given one (as they can be associated in several different relations).
   * @throws IllegalArgumentException if null parameter
   */
  public Map<ObjectName, List<String>>
  findAssociatedMBeans(ObjectName mbeanName,
      String relationTypeName,
      String roleName)
      throws IllegalArgumentException {

    if (mbeanName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "findAssociatedMBeans",
        new Object[]{mbeanName, relationTypeName, roleName});

    // Retrieves the map <relation id> -> <role names> for those
    // criterias
    Map<String, List<String>> relId2RoleNamesMap =
        findReferencingRelations(mbeanName,
            relationTypeName,
            roleName);

    Map<ObjectName, List<String>> result =
        new HashMap<ObjectName, List<String>>();

    for (String currRelId : relId2RoleNamesMap.keySet()) {

      // Retrieves ObjectNames of MBeans referenced in this relation
      //
      // Shall not throw a RelationNotFoundException if incorrect status
      // of maps :(
      Map<ObjectName, List<String>> objName2RoleNamesMap;
      try {
        objName2RoleNamesMap = getReferencedMBeans(currRelId);
      } catch (RelationNotFoundException exc) {
        throw new RuntimeException(exc.getMessage());
      }

      // For each MBean associated to given one in a relation, adds the
      // association <ObjectName> -> <relation id> into result map
      for (ObjectName currObjName : objName2RoleNamesMap.keySet()) {

        if (!(currObjName.equals(mbeanName))) {

          // Sees if this MBean is already associated to the given
          // one in another relation
          List<String> currRelIdList = result.get(currObjName);
          if (currRelIdList == null) {

            currRelIdList = new ArrayList<String>();
            currRelIdList.add(currRelId);
            result.put(currObjName, currRelIdList);

          } else {
            currRelIdList.add(currRelId);
          }
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "findAssociatedMBeans");
    return result;
  }

  /**
   * Returns the relation ids for relations of the given type.
   *
   * @param relationTypeName relation type name
   * @return an ArrayList of relation ids.
   * @throws IllegalArgumentException if null parameter
   * @throws RelationTypeNotFoundException if there is no relation type with that name.
   */
  public List<String> findRelationsOfType(String relationTypeName)
      throws IllegalArgumentException,
      RelationTypeNotFoundException {

    if (relationTypeName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "findRelationsOfType");

    // Can throw RelationTypeNotFoundException
    RelationType relType = getRelationType(relationTypeName);

    List<String> result;
    synchronized (myRelType2RelIdsMap) {
      List<String> result1 = myRelType2RelIdsMap.get(relationTypeName);
      if (result1 == null) {
        result = new ArrayList<String>();
      } else {
        result = new ArrayList<String>(result1);
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "findRelationsOfType");
    return result;
  }

  /**
   * Retrieves role value for given role name in given relation.
   *
   * @param relationId relation id
   * @param roleName name of role
   * @return the ArrayList of ObjectName objects being the role value
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if no relation with given id
   * @throws RoleNotFoundException if: <P>- there is no role with given name <P>or <P>- the role is
   * not readable.
   * @see #setRole
   */
  public List<ObjectName> getRole(String relationId,
      String roleName)
      throws RelationServiceNotRegisteredException,
      IllegalArgumentException,
      RelationNotFoundException,
      RoleNotFoundException {

    if (relationId == null || roleName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getRole", new Object[]{relationId, roleName});

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // Can throw a RelationNotFoundException
    Object relObj = getRelation(relationId);

    List<ObjectName> result;

    if (relObj instanceof RelationSupport) {
      // Internal relation
      // Can throw RoleNotFoundException
      result = cast(
          ((RelationSupport) relObj).getRoleInt(roleName,
              true,
              this,
              false));

    } else {
      // Relation MBean
      Object[] params = new Object[1];
      params[0] = roleName;
      String[] signature = new String[1];
      signature[0] = "java.lang.String";
      // Can throw MBeanException wrapping a RoleNotFoundException:
      // throw wrapped exception
      //
      // Shall not throw InstanceNotFoundException or ReflectionException
      try {
        List<ObjectName> invokeResult = cast(
            myMBeanServer.invoke(((ObjectName) relObj),
                "getRole",
                params,
                signature));
        if (invokeResult == null || invokeResult instanceof ArrayList<?>) {
          result = invokeResult;
        } else {
          result = new ArrayList<ObjectName>(invokeResult);
        }
      } catch (InstanceNotFoundException exc1) {
        throw new RuntimeException(exc1.getMessage());
      } catch (ReflectionException exc2) {
        throw new RuntimeException(exc2.getMessage());
      } catch (MBeanException exc3) {
        Exception wrappedExc = exc3.getTargetException();
        if (wrappedExc instanceof RoleNotFoundException) {
          throw ((RoleNotFoundException) wrappedExc);
        } else {
          throw new RuntimeException(wrappedExc.getMessage());
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(), "getRole");
    return result;
  }

  /**
   * Retrieves values of roles with given names in given relation.
   *
   * @param relationId relation id
   * @param roleNameArray array of names of roles to be retrieved
   * @return a RoleResult object, including a RoleList (for roles successfully retrieved) and a
   * RoleUnresolvedList (for roles not retrieved).
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if no relation with given id
   * @see #setRoles
   */
  public RoleResult getRoles(String relationId,
      String[] roleNameArray)
      throws RelationServiceNotRegisteredException,
      IllegalArgumentException,
      RelationNotFoundException {

    if (relationId == null || roleNameArray == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getRoles", relationId);

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // Can throw a RelationNotFoundException
    Object relObj = getRelation(relationId);

    RoleResult result;

    if (relObj instanceof RelationSupport) {
      // Internal relation
      result = ((RelationSupport) relObj).getRolesInt(roleNameArray,
          true,
          this);
    } else {
      // Relation MBean
      Object[] params = new Object[1];
      params[0] = roleNameArray;
      String[] signature = new String[1];
      try {
        signature[0] = (roleNameArray.getClass()).getName();
      } catch (Exception exc) {
        // OK : This is an array of java.lang.String
        //      so this should never happen...
      }
      // Shall not throw InstanceNotFoundException, ReflectionException
      // or MBeanException
      try {
        result = (RoleResult)
            (myMBeanServer.invoke(((ObjectName) relObj),
                "getRoles",
                params,
                signature));
      } catch (InstanceNotFoundException exc1) {
        throw new RuntimeException(exc1.getMessage());
      } catch (ReflectionException exc2) {
        throw new RuntimeException(exc2.getMessage());
      } catch (MBeanException exc3) {
        throw new
            RuntimeException((exc3.getTargetException()).getMessage());
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(), "getRoles");
    return result;
  }

  /**
   * Returns all roles present in the relation.
   *
   * @param relationId relation id
   * @return a RoleResult object, including a RoleList (for roles successfully retrieved) and a
   * RoleUnresolvedList (for roles not readable).
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if no relation for given id
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server
   */
  public RoleResult getAllRoles(String relationId)
      throws IllegalArgumentException,
      RelationNotFoundException,
      RelationServiceNotRegisteredException {

    if (relationId == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getRoles", relationId);

    // Can throw a RelationNotFoundException
    Object relObj = getRelation(relationId);

    RoleResult result;

    if (relObj instanceof RelationSupport) {
      // Internal relation
      result = ((RelationSupport) relObj).getAllRolesInt(true, this);

    } else {
      // Relation MBean
      // Shall not throw any Exception
      try {
        result = (RoleResult)
            (myMBeanServer.getAttribute(((ObjectName) relObj),
                "AllRoles"));
      } catch (Exception exc) {
        throw new RuntimeException(exc.getMessage());
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(), "getRoles");
    return result;
  }

  /**
   * Retrieves the number of MBeans currently referenced in the given role.
   *
   * @param relationId relation id
   * @param roleName name of role
   * @return the number of currently referenced MBeans in that role
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if no relation with given id
   * @throws RoleNotFoundException if there is no role with given name
   */
  public Integer getRoleCardinality(String relationId,
      String roleName)
      throws IllegalArgumentException,
      RelationNotFoundException,
      RoleNotFoundException {

    if (relationId == null || roleName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getRoleCardinality", new Object[]{relationId, roleName});

    // Can throw a RelationNotFoundException
    Object relObj = getRelation(relationId);

    Integer result;

    if (relObj instanceof RelationSupport) {
      // Internal relation
      // Can throw RoleNotFoundException
      result = ((RelationSupport) relObj).getRoleCardinality(roleName);

    } else {
      // Relation MBean
      Object[] params = new Object[1];
      params[0] = roleName;
      String[] signature = new String[1];
      signature[0] = "java.lang.String";
      // Can throw MBeanException wrapping RoleNotFoundException:
      // throw wrapped exception
      //
      // Shall not throw InstanceNotFoundException or ReflectionException
      try {
        result = (Integer)
            (myMBeanServer.invoke(((ObjectName) relObj),
                "getRoleCardinality",
                params,
                signature));
      } catch (InstanceNotFoundException exc1) {
        throw new RuntimeException(exc1.getMessage());
      } catch (ReflectionException exc2) {
        throw new RuntimeException(exc2.getMessage());
      } catch (MBeanException exc3) {
        Exception wrappedExc = exc3.getTargetException();
        if (wrappedExc instanceof RoleNotFoundException) {
          throw ((RoleNotFoundException) wrappedExc);
        } else {
          throw new RuntimeException(wrappedExc.getMessage());
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "getRoleCardinality");
    return result;
  }

  /**
   * Sets the given role in given relation.
   * <P>Will check the role according to its corresponding role definition
   * provided in relation's relation type
   * <P>The Relation Service will keep track of the change to keep the
   * consistency of relations by handling referenced MBean deregistrations.
   *
   * @param relationId relation id
   * @param role role to be set (name and new value)
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if no relation with given id
   * @throws RoleNotFoundException if the role does not exist or is not writable
   * @throws InvalidRoleValueException if value provided for role is not valid: <P>- the number of
   * referenced MBeans in given value is less than expected minimum degree <P>or <P>- the number of
   * referenced MBeans in provided value exceeds expected maximum degree <P>or <P>- one referenced
   * MBean in the value is not an Object of the MBean class expected for that role <P>or <P>- an
   * MBean provided for that role does not exist
   * @see #getRole
   */
  public void setRole(String relationId,
      Role role)
      throws RelationServiceNotRegisteredException,
      IllegalArgumentException,
      RelationNotFoundException,
      RoleNotFoundException,
      InvalidRoleValueException {

    if (relationId == null || role == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "setRole", new Object[]{relationId, role});

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // Can throw a RelationNotFoundException
    Object relObj = getRelation(relationId);

    if (relObj instanceof RelationSupport) {
      // Internal relation
      // Can throw RoleNotFoundException,
      // InvalidRoleValueException and
      // RelationServiceNotRegisteredException
      //
      // Shall not throw RelationTypeNotFoundException
      // (as relation exists in the RS, its relation type is known)
      try {
        ((RelationSupport) relObj).setRoleInt(role,
            true,
            this,
            false);

      } catch (RelationTypeNotFoundException exc) {
        throw new RuntimeException(exc.getMessage());
      }

    } else {
      // Relation MBean
      Object[] params = new Object[1];
      params[0] = role;
      String[] signature = new String[1];
      signature[0] = "javax.management.relation.Role";
      // Can throw MBeanException wrapping RoleNotFoundException,
      // InvalidRoleValueException
      //
      // Shall not MBeanException wrapping an MBeanException wrapping
      // RelationTypeNotFoundException, or ReflectionException, or
      // InstanceNotFoundException
      try {
        myMBeanServer.setAttribute(((ObjectName) relObj),
            new Attribute("Role", role));

      } catch (InstanceNotFoundException exc1) {
        throw new RuntimeException(exc1.getMessage());
      } catch (ReflectionException exc3) {
        throw new RuntimeException(exc3.getMessage());
      } catch (MBeanException exc2) {
        Exception wrappedExc = exc2.getTargetException();
        if (wrappedExc instanceof RoleNotFoundException) {
          throw ((RoleNotFoundException) wrappedExc);
        } else if (wrappedExc instanceof InvalidRoleValueException) {
          throw ((InvalidRoleValueException) wrappedExc);
        } else {
          throw new RuntimeException(wrappedExc.getMessage());

        }
      } catch (AttributeNotFoundException exc4) {
        throw new RuntimeException(exc4.getMessage());
      } catch (InvalidAttributeValueException exc5) {
        throw new RuntimeException(exc5.getMessage());
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(), "setRole");
    return;
  }

  /**
   * Sets the given roles in given relation.
   * <P>Will check the role according to its corresponding role definition
   * provided in relation's relation type
   * <P>The Relation Service keeps track of the changes to keep the
   * consistency of relations by handling referenced MBean deregistrations.
   *
   * @param relationId relation id
   * @param roleList list of roles to be set
   * @return a RoleResult object, including a RoleList (for roles successfully set) and a
   * RoleUnresolvedList (for roles not set).
   * @throws RelationServiceNotRegisteredException if the Relation Service is not registered in the
   * MBean Server
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if no relation with given id
   * @see #getRoles
   */
  public RoleResult setRoles(String relationId,
      RoleList roleList)
      throws RelationServiceNotRegisteredException,
      IllegalArgumentException,
      RelationNotFoundException {

    if (relationId == null || roleList == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "setRoles", new Object[]{relationId, roleList});

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // Can throw a RelationNotFoundException
    Object relObj = getRelation(relationId);

    RoleResult result;

    if (relObj instanceof RelationSupport) {
      // Internal relation
      // Can throw RelationServiceNotRegisteredException
      //
      // Shall not throw RelationTypeNotFoundException (as relation is
      // known, its relation type exists)
      try {
        result = ((RelationSupport) relObj).setRolesInt(roleList,
            true,
            this);
      } catch (RelationTypeNotFoundException exc) {
        throw new RuntimeException(exc.getMessage());
      }

    } else {
      // Relation MBean
      Object[] params = new Object[1];
      params[0] = roleList;
      String[] signature = new String[1];
      signature[0] = "javax.management.relation.RoleList";
      // Shall not throw InstanceNotFoundException or an MBeanException
      // or ReflectionException
      try {
        result = (RoleResult)
            (myMBeanServer.invoke(((ObjectName) relObj),
                "setRoles",
                params,
                signature));
      } catch (InstanceNotFoundException exc1) {
        throw new RuntimeException(exc1.getMessage());
      } catch (ReflectionException exc3) {
        throw new RuntimeException(exc3.getMessage());
      } catch (MBeanException exc2) {
        throw new
            RuntimeException((exc2.getTargetException()).getMessage());
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(), "setRoles");
    return result;
  }

  /**
   * Retrieves MBeans referenced in the various roles of the relation.
   *
   * @param relationId relation id
   * @return a HashMap mapping: <P> ObjectName {@literal ->} ArrayList of String (role names)
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if no relation for given relation id
   */
  public Map<ObjectName, List<String>>
  getReferencedMBeans(String relationId)
      throws IllegalArgumentException,
      RelationNotFoundException {

    if (relationId == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getReferencedMBeans", relationId);

    // Can throw a RelationNotFoundException
    Object relObj = getRelation(relationId);

    Map<ObjectName, List<String>> result;

    if (relObj instanceof RelationSupport) {
      // Internal relation
      result = ((RelationSupport) relObj).getReferencedMBeans();

    } else {
      // Relation MBean
      // No Exception
      try {
        result = cast(
            myMBeanServer.getAttribute(((ObjectName) relObj),
                "ReferencedMBeans"));
      } catch (Exception exc) {
        throw new RuntimeException(exc.getMessage());
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "getReferencedMBeans");
    return result;
  }

  /**
   * Returns name of associated relation type for given relation.
   *
   * @param relationId relation id
   * @return the name of the associated relation type.
   * @throws IllegalArgumentException if null parameter
   * @throws RelationNotFoundException if no relation for given relation id
   */
  public String getRelationTypeName(String relationId)
      throws IllegalArgumentException,
      RelationNotFoundException {

    if (relationId == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getRelationTypeName", relationId);

    // Can throw a RelationNotFoundException
    Object relObj = getRelation(relationId);

    String result;

    if (relObj instanceof RelationSupport) {
      // Internal relation
      result = ((RelationSupport) relObj).getRelationTypeName();

    } else {
      // Relation MBean
      // No Exception
      try {
        result = (String)
            (myMBeanServer.getAttribute(((ObjectName) relObj),
                "RelationTypeName"));
      } catch (Exception exc) {
        throw new RuntimeException(exc.getMessage());
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "getRelationTypeName");
    return result;
  }

  //
  // NotificationListener Interface
  //

  /**
   * Invoked when a JMX notification occurs.
   * Currently handles notifications for unregistration of MBeans, either
   * referenced in a relation role or being a relation itself.
   *
   * @param notif The notification.
   * @param handback An opaque object which helps the listener to associate information regarding
   * the MBean emitter (can be null).
   */
  public void handleNotification(Notification notif,
      Object handback) {

    if (notif == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "handleNotification", notif);

    if (notif instanceof MBeanServerNotification) {

      MBeanServerNotification mbsNtf = (MBeanServerNotification) notif;
      String ntfType = notif.getType();

      if (ntfType.equals(
          MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
        ObjectName mbeanName =
            ((MBeanServerNotification) notif).getMBeanName();

        // Note: use a flag to block access to
        // myRefedMBeanObjName2RelIdsMap only for a quick access
        boolean isRefedMBeanFlag = false;
        synchronized (myRefedMBeanObjName2RelIdsMap) {

          if (myRefedMBeanObjName2RelIdsMap.containsKey(mbeanName)) {
            // Unregistration of a referenced MBean
            synchronized (myUnregNtfList) {
              myUnregNtfList.add(mbsNtf);
            }
            isRefedMBeanFlag = true;
          }
          if (isRefedMBeanFlag && myPurgeFlag) {
            // Immediate purge
            // Can throw RelationServiceNotRegisteredException
            // but assume that will be fine :)
            try {
              purgeRelations();
            } catch (Exception exc) {
              throw new RuntimeException(exc.getMessage());
            }
          }
        }

        // Note: do both tests as a relation can be an MBean and be
        //       itself referenced in another relation :)
        String relId;
        synchronized (myRelMBeanObjName2RelIdMap) {
          relId = myRelMBeanObjName2RelIdMap.get(mbeanName);
        }
        if (relId != null) {
          // Unregistration of a relation MBean
          // Can throw RelationTypeNotFoundException,
          // RelationServiceNotRegisteredException
          //
          // Shall not throw RelationTypeNotFoundException or
          // InstanceNotFoundException
          try {
            removeRelation(relId);
          } catch (Exception exc) {
            throw new RuntimeException(exc.getMessage());
          }
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "handleNotification");
    return;
  }

  //
  // NotificationBroadcaster interface
  //

  /**
   * Returns a NotificationInfo object containing the name of the Java class
   * of the notification and the notification types sent.
   */
  public MBeanNotificationInfo[] getNotificationInfo() {

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getNotificationInfo");

    String ntfClass = "javax.management.relation.RelationNotification";

    String[] ntfTypes = new String[]{
        RelationNotification.RELATION_BASIC_CREATION,
        RelationNotification.RELATION_MBEAN_CREATION,
        RelationNotification.RELATION_BASIC_UPDATE,
        RelationNotification.RELATION_MBEAN_UPDATE,
        RelationNotification.RELATION_BASIC_REMOVAL,
        RelationNotification.RELATION_MBEAN_REMOVAL,
    };

    String ntfDesc = "Sent when a relation is created, updated or deleted.";

    MBeanNotificationInfo ntfInfo =
        new MBeanNotificationInfo(ntfTypes, ntfClass, ntfDesc);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "getNotificationInfo");
    return new MBeanNotificationInfo[]{ntfInfo};
  }

  //
  // Misc
  //

  // Adds given object as a relation type.
  //
  // -param relationTypeObj  relation type object
  //
  // -exception IllegalArgumentException  if null parameter
  // -exception InvalidRelationTypeException  if there is already a relation
  //  type with that name
  private void addRelationTypeInt(RelationType relationTypeObj)
      throws IllegalArgumentException,
      InvalidRelationTypeException {

    if (relationTypeObj == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "addRelationTypeInt");

    String relTypeName = relationTypeObj.getRelationTypeName();

    // Checks that there is not already a relation type with that name
    // existing in the Relation Service
    try {
      // Can throw a RelationTypeNotFoundException (in fact should ;)
      RelationType relType = getRelationType(relTypeName);

      if (relType != null) {
        String excMsg = "There is already a relation type in the Relation Service with name ";
        StringBuilder excMsgStrB = new StringBuilder(excMsg);
        excMsgStrB.append(relTypeName);
        throw new InvalidRelationTypeException(excMsgStrB.toString());
      }

    } catch (RelationTypeNotFoundException exc) {
      // OK : The RelationType could not be found.
    }

    // Adds the relation type
    synchronized (myRelType2ObjMap) {
      myRelType2ObjMap.put(relTypeName, relationTypeObj);
    }

    if (relationTypeObj instanceof RelationTypeSupport) {
      ((RelationTypeSupport) relationTypeObj).setRelationServiceFlag(true);
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "addRelationTypeInt");
    return;
  }

  // Retrieves relation type with given name
  //
  // -param relationTypeName  expected name of a relation type created in the
  //  Relation Service
  //
  // -return RelationType object corresponding to given name
  //
  // -exception IllegalArgumentException  if null parameter
  // -exception RelationTypeNotFoundException  if no relation type for that
  //  name created in Relation Service
  //
  RelationType getRelationType(String relationTypeName)
      throws IllegalArgumentException,
      RelationTypeNotFoundException {

    if (relationTypeName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getRelationType", relationTypeName);

    // No null relation type accepted, so can use get()
    RelationType relType;
    synchronized (myRelType2ObjMap) {
      relType = (myRelType2ObjMap.get(relationTypeName));
    }

    if (relType == null) {
      String excMsg = "No relation type created in the Relation Service with the name ";
      StringBuilder excMsgStrB = new StringBuilder(excMsg);
      excMsgStrB.append(relationTypeName);
      throw new RelationTypeNotFoundException(excMsgStrB.toString());
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "getRelationType");
    return relType;
  }

  // Retrieves relation corresponding to given relation id.
  // Returns either:
  // - a RelationSupport object if the relation is internal
  // or
  // - the ObjectName of the corresponding MBean
  //
  // -param relationId  expected relation id
  //
  // -return RelationSupport object or ObjectName of relation with given id
  //
  // -exception IllegalArgumentException  if null parameter
  // -exception RelationNotFoundException  if no relation for that
  //  relation id created in Relation Service
  //
  Object getRelation(String relationId)
      throws IllegalArgumentException,
      RelationNotFoundException {

    if (relationId == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "getRelation", relationId);

    // No null relation  accepted, so can use get()
    Object rel;
    synchronized (myRelId2ObjMap) {
      rel = myRelId2ObjMap.get(relationId);
    }

    if (rel == null) {
      String excMsg = "No relation associated to relation id " + relationId;
      throw new RelationNotFoundException(excMsg);
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "getRelation");
    return rel;
  }

  // Adds a new MBean reference (reference to an ObjectName) in the
  // referenced MBean map (myRefedMBeanObjName2RelIdsMap).
  //
  // -param objectName  ObjectName of new referenced MBean
  // -param relationId  relation id of the relation where the MBean is
  //  referenced
  // -param roleName  name of the role where the MBean is referenced
  //
  // -return boolean:
  //  - true  if the MBean was not referenced before, so really a new
  //    reference
  //  - false else
  //
  // -exception IllegalArgumentException  if null parameter
  private boolean addNewMBeanReference(ObjectName objectName,
      String relationId,
      String roleName)
      throws IllegalArgumentException {

    if (objectName == null ||
        relationId == null ||
        roleName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "addNewMBeanReference",
        new Object[]{objectName, relationId, roleName});

    boolean isNewFlag = false;

    synchronized (myRefedMBeanObjName2RelIdsMap) {

      // Checks if the MBean was already referenced
      // No null value allowed, use get() directly
      Map<String, List<String>> mbeanRefMap =
          myRefedMBeanObjName2RelIdsMap.get(objectName);

      if (mbeanRefMap == null) {
        // MBean not referenced in any relation yet

        isNewFlag = true;

        // List of roles where the MBean is referenced in given
        // relation
        List<String> roleNames = new ArrayList<String>();
        roleNames.add(roleName);

        // Map of relations where the MBean is referenced
        mbeanRefMap = new HashMap<String, List<String>>();
        mbeanRefMap.put(relationId, roleNames);

        myRefedMBeanObjName2RelIdsMap.put(objectName, mbeanRefMap);

      } else {
        // MBean already referenced in at least another relation
        // Checks if already referenced in another role in current
        // relation
        List<String> roleNames = mbeanRefMap.get(relationId);

        if (roleNames == null) {
          // MBean not referenced in current relation

          // List of roles where the MBean is referenced in given
          // relation
          roleNames = new ArrayList<String>();
          roleNames.add(roleName);

          // Adds new reference done in current relation
          mbeanRefMap.put(relationId, roleNames);

        } else {
          // MBean already referenced in current relation in another
          // role
          // Adds new reference done
          roleNames.add(roleName);
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "addNewMBeanReference");
    return isNewFlag;
  }

  // Removes an obsolete MBean reference (reference to an ObjectName) in
  // the referenced MBean map (myRefedMBeanObjName2RelIdsMap).
  //
  // -param objectName  ObjectName of MBean no longer referenced
  // -param relationId  relation id of the relation where the MBean was
  //  referenced
  // -param roleName  name of the role where the MBean was referenced
  // -param allRolesFlag  flag, if true removes reference to MBean for all
  //  roles in the relation, not only for the one above
  //
  // -return boolean:
  //  - true  if the MBean is no longer reference in any relation
  //  - false else
  //
  // -exception IllegalArgumentException  if null parameter
  private boolean removeMBeanReference(ObjectName objectName,
      String relationId,
      String roleName,
      boolean allRolesFlag)
      throws IllegalArgumentException {

    if (objectName == null ||
        relationId == null ||
        roleName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "removeMBeanReference",
        new Object[]{objectName, relationId, roleName, allRolesFlag});

    boolean noLongerRefFlag = false;

    synchronized (myRefedMBeanObjName2RelIdsMap) {

      // Retrieves the set of relations (designed via their relation ids)
      // where the MBean is referenced
      // Note that it is possible that the MBean has already been removed
      // from the internal map: this is the case when the MBean is
      // unregistered, the role is updated, then we arrive here.
      Map<String, List<String>> mbeanRefMap =
          (myRefedMBeanObjName2RelIdsMap.get(objectName));

      if (mbeanRefMap == null) {
        // The MBean is no longer referenced
        RELATION_LOGGER.exiting(RelationService.class.getName(),
            "removeMBeanReference");
        return true;
      }

      List<String> roleNames = null;
      if (!allRolesFlag) {
        // Now retrieves the roles of current relation where the MBean
        // was referenced
        roleNames = mbeanRefMap.get(relationId);

        // Removes obsolete reference to role
        int obsRefIdx = roleNames.indexOf(roleName);
        if (obsRefIdx != -1) {
          roleNames.remove(obsRefIdx);
        }
      }

      // Checks if there is still at least one role in current relation
      // where the MBean is referenced
      if (roleNames.isEmpty() || allRolesFlag) {
        // MBean no longer referenced in current relation: removes
        // entry
        mbeanRefMap.remove(relationId);
      }

      // Checks if the MBean is still referenced in at least on relation
      if (mbeanRefMap.isEmpty()) {
        // MBean no longer referenced in any relation: removes entry
        myRefedMBeanObjName2RelIdsMap.remove(objectName);
        noLongerRefFlag = true;
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "removeMBeanReference");
    return noLongerRefFlag;
  }

  // Updates the listener registered to the MBean Server to be informed of
  // referenced MBean deregistrations
  //
  // -param newRefList  ArrayList of ObjectNames for new references done
  //  to MBeans (can be null)
  // -param obsoleteRefList  ArrayList of ObjectNames for obsolete references
  //  to MBeans (can be null)
  //
  // -exception RelationServiceNotRegisteredException  if the Relation
  //  Service is not registered in the MBean Server.
  private void updateUnregistrationListener(List<ObjectName> newRefList,
      List<ObjectName> obsoleteRefList)
      throws RelationServiceNotRegisteredException {

    if (newRefList != null && obsoleteRefList != null) {
      if (newRefList.isEmpty() && obsoleteRefList.isEmpty()) {
        // Nothing to do :)
        return;
      }
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "updateUnregistrationListener",
        new Object[]{newRefList, obsoleteRefList});

    // Can throw RelationServiceNotRegisteredException
    isActive();

    if (newRefList != null || obsoleteRefList != null) {

      boolean newListenerFlag = false;
      if (myUnregNtfFilter == null) {
        // Initialize it to be able to synchronise it :)
        myUnregNtfFilter = new MBeanServerNotificationFilter();
        newListenerFlag = true;
      }

      synchronized (myUnregNtfFilter) {

        // Enables ObjectNames in newRefList
        if (newRefList != null) {
          for (ObjectName newObjName : newRefList) {
            myUnregNtfFilter.enableObjectName(newObjName);
          }
        }

        if (obsoleteRefList != null) {
          // Disables ObjectNames in obsoleteRefList
          for (ObjectName obsObjName : obsoleteRefList) {
            myUnregNtfFilter.disableObjectName(obsObjName);
          }
        }

// Under test
        if (newListenerFlag) {
          try {
            myMBeanServer.addNotificationListener(
                MBeanServerDelegate.DELEGATE_NAME,
                this,
                myUnregNtfFilter,
                null);
          } catch (InstanceNotFoundException exc) {
            throw new
                RelationServiceNotRegisteredException(exc.getMessage());
          }
        }
// End test

//              if (!newListenerFlag) {
        // The Relation Service was already registered as a
        // listener:
        // removes it
        // Shall not throw InstanceNotFoundException (as the
        // MBean Server Delegate is expected to exist) or
        // ListenerNotFoundException (as it has been checked above
        // that the Relation Service is registered)
//                  try {
//                      myMBeanServer.removeNotificationListener(
//                              MBeanServerDelegate.DELEGATE_NAME,
//                              this);
//                  } catch (InstanceNotFoundException exc1) {
//                      throw new RuntimeException(exc1.getMessage());
//                  } catch (ListenerNotFoundException exc2) {
//                      throw new
//                          RelationServiceNotRegisteredException(exc2.getMessage());
//                  }
//              }

        // Adds Relation Service with current filter
        // Can throw InstanceNotFoundException if the Relation
        // Service is not registered, to be transformed into
        // RelationServiceNotRegisteredException
        //
        // Assume that there will not be any InstanceNotFoundException
        // for the MBean Server Delegate :)
//              try {
//                  myMBeanServer.addNotificationListener(
//                              MBeanServerDelegate.DELEGATE_NAME,
//                              this,
//                              myUnregNtfFilter,
//                              null);
//              } catch (InstanceNotFoundException exc) {
//                  throw new
//                     RelationServiceNotRegisteredException(exc.getMessage());
//              }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "updateUnregistrationListener");
    return;
  }

  // Adds a relation (being either a RelationSupport object or an MBean
  // referenced using its ObjectName) in the Relation Service.
  // Will send a notification RelationNotification with type:
  // - RelationNotification.RELATION_BASIC_CREATION for internal relation
  //   creation
  // - RelationNotification.RELATION_MBEAN_CREATION for an MBean being added
  //   as a relation.
  //
  // -param relationBaseFlag  flag true if the relation is a RelationSupport
  //  object, false if it is an MBean
  // -param relationObj  RelationSupport object (if relation is internal)
  // -param relationObjName  ObjectName of the MBean to be added as a relation
  //  (only for the relation MBean)
  // -param relationId  relation identifier, to uniquely identify the relation
  //  inside the Relation Service
  // -param relationTypeName  name of the relation type (has to be created
  //  in the Relation Service)
  // -param roleList  role list to initialize roles of the relation
  //  (can be null)
  //
  // -exception IllegalArgumentException  if null paramater
  // -exception RelationServiceNotRegisteredException  if the Relation
  //  Service is not registered in the MBean Server
  // -exception RoleNotFoundException  if a value is provided for a role
  //  that does not exist in the relation type
  // -exception InvalidRelationIdException  if relation id already used
  // -exception RelationTypeNotFoundException  if relation type not known in
  //  Relation Service
  // -exception InvalidRoleValueException if:
  //  - the same role name is used for two different roles
  //  - the number of referenced MBeans in given value is less than
  //    expected minimum degree
  //  - the number of referenced MBeans in provided value exceeds expected
  //    maximum degree
  //  - one referenced MBean in the value is not an Object of the MBean
  //    class expected for that role
  //  - an MBean provided for that role does not exist
  private void addRelationInt(boolean relationBaseFlag,
      RelationSupport relationObj,
      ObjectName relationObjName,
      String relationId,
      String relationTypeName,
      RoleList roleList)
      throws IllegalArgumentException,
      RelationServiceNotRegisteredException,
      RoleNotFoundException,
      InvalidRelationIdException,
      RelationTypeNotFoundException,
      InvalidRoleValueException {

    if (relationId == null ||
        relationTypeName == null ||
        (relationBaseFlag &&
            (relationObj == null ||
                relationObjName != null)) ||
        (!relationBaseFlag &&
            (relationObjName == null ||
                relationObj != null))) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "addRelationInt", new Object[]{relationBaseFlag, relationObj,
            relationObjName, relationId, relationTypeName, roleList});

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // Checks if there is already a relation with given id
    try {
      // Can throw a RelationNotFoundException (in fact should :)
      Object rel = getRelation(relationId);

      if (rel != null) {
        // There is already a relation with that id
        String excMsg = "There is already a relation with id ";
        StringBuilder excMsgStrB = new StringBuilder(excMsg);
        excMsgStrB.append(relationId);
        throw new InvalidRelationIdException(excMsgStrB.toString());
      }
    } catch (RelationNotFoundException exc) {
      // OK : The Relation could not be found.
    }

    // Retrieves the relation type
    // Can throw RelationTypeNotFoundException
    RelationType relType = getRelationType(relationTypeName);

    // Checks that each provided role conforms to its role info provided in
    // the relation type
    // First retrieves a local list of the role infos of the relation type
    // to see which roles have not been initialized
    // Note: no need to test if list not null before cloning, not allowed
    //       to have an empty relation type.
    List<RoleInfo> roleInfoList = new ArrayList<RoleInfo>(relType.getRoleInfos());

    if (roleList != null) {

      for (Role currRole : roleList.asList()) {
        String currRoleName = currRole.getRoleName();
        List<ObjectName> currRoleValue = currRole.getRoleValue();
        // Retrieves corresponding role info
        // Can throw a RoleInfoNotFoundException to be converted into a
        // RoleNotFoundException
        RoleInfo roleInfo;
        try {
          roleInfo = relType.getRoleInfo(currRoleName);
        } catch (RoleInfoNotFoundException exc) {
          throw new RoleNotFoundException(exc.getMessage());
        }

        // Checks that role conforms to role info,
        Integer status = checkRoleInt(2,
            currRoleName,
            currRoleValue,
            roleInfo,
            false);
        int pbType = status.intValue();
        if (pbType != 0) {
          // A problem has occurred: throws appropriate exception
          // here InvalidRoleValueException
          throwRoleProblemException(pbType, currRoleName);
        }

        // Removes role info for that list from list of role infos for
        // roles to be defaulted
        int roleInfoIdx = roleInfoList.indexOf(roleInfo);
        // Note: no need to check if != -1, MUST be there :)
        roleInfoList.remove(roleInfoIdx);
      }
    }

    // Initializes roles not initialized by roleList
    // Can throw InvalidRoleValueException
    initializeMissingRoles(relationBaseFlag,
        relationObj,
        relationObjName,
        relationId,
        relationTypeName,
        roleInfoList);

    // Creation of relation successfull!!!!

    // Updates internal maps
    // Relation id to object map
    synchronized (myRelId2ObjMap) {
      if (relationBaseFlag) {
        // Note: do not clone relation object, created by us :)
        myRelId2ObjMap.put(relationId, relationObj);
      } else {
        myRelId2ObjMap.put(relationId, relationObjName);
      }
    }

    // Relation id to relation type name map
    synchronized (myRelId2RelTypeMap) {
      myRelId2RelTypeMap.put(relationId,
          relationTypeName);
    }

    // Relation type to relation id map
    synchronized (myRelType2RelIdsMap) {
      List<String> relIdList =
          myRelType2RelIdsMap.get(relationTypeName);
      boolean firstRelFlag = false;
      if (relIdList == null) {
        firstRelFlag = true;
        relIdList = new ArrayList<String>();
      }
      relIdList.add(relationId);
      if (firstRelFlag) {
        myRelType2RelIdsMap.put(relationTypeName, relIdList);
      }
    }

    // Referenced MBean to relation id map
    // Only role list parameter used, as default initialization of roles
    // done automatically in initializeMissingRoles() sets each
    // uninitialized role to an empty value.
    for (Role currRole : roleList.asList()) {
      // Creates a dummy empty ArrayList of ObjectNames to be the old
      // role value :)
      List<ObjectName> dummyList = new ArrayList<ObjectName>();
      // Will not throw a RelationNotFoundException (as the RelId2Obj map
      // has been updated above) so catch it :)
      try {
        updateRoleMap(relationId, currRole, dummyList);

      } catch (RelationNotFoundException exc) {
        // OK : The Relation could not be found.
      }
    }

    // Sends a notification for relation creation
    // Will not throw RelationNotFoundException so catch it :)
    try {
      sendRelationCreationNotification(relationId);

    } catch (RelationNotFoundException exc) {
      // OK : The Relation could not be found.
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "addRelationInt");
    return;
  }

  // Checks that given role conforms to given role info.
  //
  // -param chkType  type of check:
  //  - 1: read, just check read access
  //  - 2: write, check value and write access if writeChkFlag
  // -param roleName  role name
  // -param roleValue  role value
  // -param roleInfo  corresponding role info
  // -param writeChkFlag  boolean to specify a current write access and
  //  to check it
  //
  // -return Integer with value:
  //  - 0: ok
  //  - RoleStatus.NO_ROLE_WITH_NAME
  //  - RoleStatus.ROLE_NOT_READABLE
  //  - RoleStatus.ROLE_NOT_WRITABLE
  //  - RoleStatus.LESS_THAN_MIN_ROLE_DEGREE
  //  - RoleStatus.MORE_THAN_MAX_ROLE_DEGREE
  //  - RoleStatus.REF_MBEAN_OF_INCORRECT_CLASS
  //  - RoleStatus.REF_MBEAN_NOT_REGISTERED
  //
  // -exception IllegalArgumentException  if null parameter
  private Integer checkRoleInt(int chkType,
      String roleName,
      List<ObjectName> roleValue,
      RoleInfo roleInfo,
      boolean writeChkFlag)
      throws IllegalArgumentException {

    if (roleName == null ||
        roleInfo == null ||
        (chkType == 2 && roleValue == null)) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "checkRoleInt", new Object[]{chkType, roleName,
            roleValue, roleInfo, writeChkFlag});

    // Compares names
    String expName = roleInfo.getName();
    if (!(roleName.equals(expName))) {
      RELATION_LOGGER.exiting(RelationService.class.getName(),
          "checkRoleInt");
      return Integer.valueOf(RoleStatus.NO_ROLE_WITH_NAME);
    }

    // Checks read access if required
    if (chkType == 1) {
      boolean isReadable = roleInfo.isReadable();
      if (!isReadable) {
        RELATION_LOGGER.exiting(RelationService.class.getName(),
            "checkRoleInt");
        return Integer.valueOf(RoleStatus.ROLE_NOT_READABLE);
      } else {
        // End of check :)
        RELATION_LOGGER.exiting(RelationService.class.getName(),
            "checkRoleInt");
        return new Integer(0);
      }
    }

    // Checks write access if required
    if (writeChkFlag) {
      boolean isWritable = roleInfo.isWritable();
      if (!isWritable) {
        RELATION_LOGGER.exiting(RelationService.class.getName(),
            "checkRoleInt");
        return new Integer(RoleStatus.ROLE_NOT_WRITABLE);
      }
    }

    int refNbr = roleValue.size();

    // Checks minimum cardinality
    boolean chkMinFlag = roleInfo.checkMinDegree(refNbr);
    if (!chkMinFlag) {
      RELATION_LOGGER.exiting(RelationService.class.getName(),
          "checkRoleInt");
      return new Integer(RoleStatus.LESS_THAN_MIN_ROLE_DEGREE);
    }

    // Checks maximum cardinality
    boolean chkMaxFlag = roleInfo.checkMaxDegree(refNbr);
    if (!chkMaxFlag) {
      RELATION_LOGGER.exiting(RelationService.class.getName(),
          "checkRoleInt");
      return new Integer(RoleStatus.MORE_THAN_MAX_ROLE_DEGREE);
    }

    // Verifies that each referenced MBean is registered in the MBean
    // Server and that it is an instance of the class specified in the
    // role info, or of a subclass of it
    // Note that here again this is under the assumption that
    // referenced MBeans, relation MBeans and the Relation Service are
    // registered in the same MBean Server.
    String expClassName = roleInfo.getRefMBeanClassName();

    for (ObjectName currObjName : roleValue) {

      // Checks it is registered
      if (currObjName == null) {
        RELATION_LOGGER.exiting(RelationService.class.getName(),
            "checkRoleInt");
        return new Integer(RoleStatus.REF_MBEAN_NOT_REGISTERED);
      }

      // Checks if it is of the correct class
      // Can throw an InstanceNotFoundException, if MBean not registered
      try {
        boolean classSts = myMBeanServer.isInstanceOf(currObjName,
            expClassName);
        if (!classSts) {
          RELATION_LOGGER.exiting(RelationService.class.getName(),
              "checkRoleInt");
          return new Integer(RoleStatus.REF_MBEAN_OF_INCORRECT_CLASS);
        }

      } catch (InstanceNotFoundException exc) {
        RELATION_LOGGER.exiting(RelationService.class.getName(),
            "checkRoleInt");
        return new Integer(RoleStatus.REF_MBEAN_NOT_REGISTERED);
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "checkRoleInt");
    return new Integer(0);
  }

  // Initializes roles associated to given role infos to default value (empty
  // ArrayList of ObjectNames) in given relation.
  // It will succeed for every role except if the role info has a minimum
  // cardinality greater than 0. In that case, an InvalidRoleValueException
  // will be raised.
  //
  // -param relationBaseFlag  flag true if the relation is a RelationSupport
  //  object, false if it is an MBean
  // -param relationObj  RelationSupport object (if relation is internal)
  // -param relationObjName  ObjectName of the MBean to be added as a relation
  //  (only for the relation MBean)
  // -param relationId  relation id
  // -param relationTypeName  name of the relation type (has to be created
  //  in the Relation Service)
  // -param roleInfoList  list of role infos for roles to be defaulted
  //
  // -exception IllegalArgumentException  if null paramater
  // -exception RelationServiceNotRegisteredException  if the Relation
  //  Service is not registered in the MBean Server
  // -exception InvalidRoleValueException  if role must have a non-empty
  //  value

  // Revisit [cebro] Handle CIM qualifiers as REQUIRED to detect roles which
  //    should have been initialized by the user
  private void initializeMissingRoles(boolean relationBaseFlag,
      RelationSupport relationObj,
      ObjectName relationObjName,
      String relationId,
      String relationTypeName,
      List<RoleInfo> roleInfoList)
      throws IllegalArgumentException,
      RelationServiceNotRegisteredException,
      InvalidRoleValueException {

    if ((relationBaseFlag &&
        (relationObj == null ||
            relationObjName != null)) ||
        (!relationBaseFlag &&
            (relationObjName == null ||
                relationObj != null)) ||
        relationId == null ||
        relationTypeName == null ||
        roleInfoList == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "initializeMissingRoles", new Object[]{relationBaseFlag,
            relationObj, relationObjName, relationId, relationTypeName,
            roleInfoList});

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // For each role info (corresponding to a role not initialized by the
    // role list provided by the user), try to set in the relation a role
    // with an empty list of ObjectNames.
    // A check is performed to verify that the role can be set to an
    // empty value, according to its minimum cardinality
    for (RoleInfo currRoleInfo : roleInfoList) {

      String roleName = currRoleInfo.getName();

      // Creates an empty value
      List<ObjectName> emptyValue = new ArrayList<ObjectName>();
      // Creates a role
      Role role = new Role(roleName, emptyValue);

      if (relationBaseFlag) {

        // Internal relation
        // Can throw InvalidRoleValueException
        //
        // Will not throw RoleNotFoundException (role to be
        // initialized), or RelationNotFoundException, or
        // RelationTypeNotFoundException
        try {
          relationObj.setRoleInt(role, true, this, false);

        } catch (RoleNotFoundException exc1) {
          throw new RuntimeException(exc1.getMessage());
        } catch (RelationNotFoundException exc2) {
          throw new RuntimeException(exc2.getMessage());
        } catch (RelationTypeNotFoundException exc3) {
          throw new RuntimeException(exc3.getMessage());
        }

      } else {

        // Relation is an MBean
        // Use standard setRole()
        Object[] params = new Object[1];
        params[0] = role;
        String[] signature = new String[1];
        signature[0] = "javax.management.relation.Role";
        // Can throw MBeanException wrapping
        // InvalidRoleValueException. Returns the target exception to
        // be homogeneous.
        //
        // Will not throw MBeanException (wrapping
        // RoleNotFoundException or MBeanException) or
        // InstanceNotFoundException, or ReflectionException
        //
        // Again here the assumption is that the Relation Service and
        // the relation MBeans are registered in the same MBean Server.
        try {
          myMBeanServer.setAttribute(relationObjName,
              new Attribute("Role", role));

        } catch (InstanceNotFoundException exc1) {
          throw new RuntimeException(exc1.getMessage());
        } catch (ReflectionException exc3) {
          throw new RuntimeException(exc3.getMessage());
        } catch (MBeanException exc2) {
          Exception wrappedExc = exc2.getTargetException();
          if (wrappedExc instanceof InvalidRoleValueException) {
            throw ((InvalidRoleValueException) wrappedExc);
          } else {
            throw new RuntimeException(wrappedExc.getMessage());
          }
        } catch (AttributeNotFoundException exc4) {
          throw new RuntimeException(exc4.getMessage());
        } catch (InvalidAttributeValueException exc5) {
          throw new RuntimeException(exc5.getMessage());
        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "initializeMissingRoles");
    return;
  }

  // Throws an exception corresponding to a given problem type
  //
  // -param pbType  possible problem, defined in RoleUnresolved
  // -param roleName  role name
  //
  // -exception IllegalArgumentException  if null parameter
  // -exception RoleNotFoundException  for problems:
  //  - NO_ROLE_WITH_NAME
  //  - ROLE_NOT_READABLE
  //  - ROLE_NOT_WRITABLE
  // -exception InvalidRoleValueException  for problems:
  //  - LESS_THAN_MIN_ROLE_DEGREE
  //  - MORE_THAN_MAX_ROLE_DEGREE
  //  - REF_MBEAN_OF_INCORRECT_CLASS
  //  - REF_MBEAN_NOT_REGISTERED
  static void throwRoleProblemException(int pbType,
      String roleName)
      throws IllegalArgumentException,
      RoleNotFoundException,
      InvalidRoleValueException {

    if (roleName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    // Exception type: 1 = RoleNotFoundException
    //                 2 = InvalidRoleValueException
    int excType = 0;

    String excMsgPart = null;

    switch (pbType) {
      case RoleStatus.NO_ROLE_WITH_NAME:
        excMsgPart = " does not exist in relation.";
        excType = 1;
        break;
      case RoleStatus.ROLE_NOT_READABLE:
        excMsgPart = " is not readable.";
        excType = 1;
        break;
      case RoleStatus.ROLE_NOT_WRITABLE:
        excMsgPart = " is not writable.";
        excType = 1;
        break;
      case RoleStatus.LESS_THAN_MIN_ROLE_DEGREE:
        excMsgPart = " has a number of MBean references less than the expected minimum degree.";
        excType = 2;
        break;
      case RoleStatus.MORE_THAN_MAX_ROLE_DEGREE:
        excMsgPart = " has a number of MBean references greater than the expected maximum degree.";
        excType = 2;
        break;
      case RoleStatus.REF_MBEAN_OF_INCORRECT_CLASS:
        excMsgPart = " has an MBean reference to an MBean not of the expected class of references for that role.";
        excType = 2;
        break;
      case RoleStatus.REF_MBEAN_NOT_REGISTERED:
        excMsgPart = " has a reference to null or to an MBean not registered.";
        excType = 2;
        break;
    }
    // No default as we must have been in one of those cases

    StringBuilder excMsgStrB = new StringBuilder(roleName);
    excMsgStrB.append(excMsgPart);
    String excMsg = excMsgStrB.toString();
    if (excType == 1) {
      throw new RoleNotFoundException(excMsg);

    } else if (excType == 2) {
      throw new InvalidRoleValueException(excMsg);
    }
  }

  // Sends a notification of given type, with given parameters
  //
  // -param intNtfType  integer to represent notification type:
  //  - 1 : create
  //  - 2 : update
  //  - 3 : delete
  // -param message  human-readable message
  // -param relationId  relation id of the created/updated/deleted relation
  // -param unregMBeanList  list of ObjectNames of referenced MBeans
  //  expected to be unregistered due to relation removal (only for removal,
  //  due to CIM qualifiers, can be null)
  // -param roleName  role name
  // -param roleNewValue  role new value (ArrayList of ObjectNames)
  // -param oldValue  old role value (ArrayList of ObjectNames)
  //
  // -exception IllegalArgument  if null parameter
  // -exception RelationNotFoundException  if no relation for given id
  private void sendNotificationInt(int intNtfType,
      String message,
      String relationId,
      List<ObjectName> unregMBeanList,
      String roleName,
      List<ObjectName> roleNewValue,
      List<ObjectName> oldValue)
      throws IllegalArgumentException,
      RelationNotFoundException {

    if (message == null ||
        relationId == null ||
        (intNtfType != 3 && unregMBeanList != null) ||
        (intNtfType == 2 &&
            (roleName == null ||
                roleNewValue == null ||
                oldValue == null))) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "sendNotificationInt", new Object[]{intNtfType, message,
            relationId, unregMBeanList, roleName, roleNewValue, oldValue});

    // Relation type name
    // Note: do not use getRelationTypeName() as if it is a relation MBean
    //       it is already unregistered.
    String relTypeName;
    synchronized (myRelId2RelTypeMap) {
      relTypeName = (myRelId2RelTypeMap.get(relationId));
    }

    // ObjectName (for a relation MBean)
    // Can also throw a RelationNotFoundException, but detected above
    ObjectName relObjName = isRelationMBean(relationId);

    String ntfType = null;
    if (relObjName != null) {
      switch (intNtfType) {
        case 1:
          ntfType = RelationNotification.RELATION_MBEAN_CREATION;
          break;
        case 2:
          ntfType = RelationNotification.RELATION_MBEAN_UPDATE;
          break;
        case 3:
          ntfType = RelationNotification.RELATION_MBEAN_REMOVAL;
          break;
      }
    } else {
      switch (intNtfType) {
        case 1:
          ntfType = RelationNotification.RELATION_BASIC_CREATION;
          break;
        case 2:
          ntfType = RelationNotification.RELATION_BASIC_UPDATE;
          break;
        case 3:
          ntfType = RelationNotification.RELATION_BASIC_REMOVAL;
          break;
      }
    }

    // Sequence number
    Long seqNo = atomicSeqNo.incrementAndGet();

    // Timestamp
    Date currDate = new Date();
    long timeStamp = currDate.getTime();

    RelationNotification ntf = null;

    if (ntfType.equals(RelationNotification.RELATION_BASIC_CREATION) ||
        ntfType.equals(RelationNotification.RELATION_MBEAN_CREATION) ||
        ntfType.equals(RelationNotification.RELATION_BASIC_REMOVAL) ||
        ntfType.equals(RelationNotification.RELATION_MBEAN_REMOVAL))

    // Creation or removal
    {
      ntf = new RelationNotification(ntfType,
          this,
          seqNo.longValue(),
          timeStamp,
          message,
          relationId,
          relTypeName,
          relObjName,
          unregMBeanList);
    } else if (ntfType.equals(RelationNotification.RELATION_BASIC_UPDATE)
        ||
        ntfType.equals(RelationNotification.RELATION_MBEAN_UPDATE)) {
      // Update
      ntf = new RelationNotification(ntfType,
          this,
          seqNo.longValue(),
          timeStamp,
          message,
          relationId,
          relTypeName,
          relObjName,
          roleName,
          roleNewValue,
          oldValue);
    }

    sendNotification(ntf);

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "sendNotificationInt");
    return;
  }

  // Checks, for the unregistration of an MBean referenced in the roles given
  // in parameter, if the relation has to be removed or not, regarding
  // expected minimum role cardinality and current number of
  // references in each role after removal of the current one.
  // If the relation is kept, calls handleMBeanUnregistration() callback of
  // the relation to update it.
  //
  // -param relationId  relation id
  // -param objectName  ObjectName of the unregistered MBean
  // -param roleNameList  list of names of roles where the unregistered
  //  MBean is referenced.
  //
  // -exception IllegalArgumentException  if null parameter
  // -exception RelationServiceNotRegisteredException  if the Relation
  //  Service is not registered in the MBean Server
  // -exception RelationNotFoundException  if unknown relation id
  // -exception RoleNotFoundException  if one role given as parameter does
  //  not exist in the relation
  private void handleReferenceUnregistration(String relationId,
      ObjectName objectName,
      List<String> roleNameList)
      throws IllegalArgumentException,
      RelationServiceNotRegisteredException,
      RelationNotFoundException,
      RoleNotFoundException {

    if (relationId == null ||
        roleNameList == null ||
        objectName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(RelationService.class.getName(),
        "handleReferenceUnregistration",
        new Object[]{relationId, objectName, roleNameList});

    // Can throw RelationServiceNotRegisteredException
    isActive();

    // Retrieves the relation type name of the relation
    // Can throw RelationNotFoundException
    String currRelTypeName = getRelationTypeName(relationId);

    // Retrieves the relation
    // Can throw RelationNotFoundException, but already detected above
    Object relObj = getRelation(relationId);

    // Flag to specify if the relation has to be deleted
    boolean deleteRelFlag = false;

    for (String currRoleName : roleNameList) {

      if (deleteRelFlag) {
        break;
      }

      // Retrieves number of MBeans currently referenced in role
      // BEWARE! Do not use getRole() as role may be not readable
      //
      // Can throw RelationNotFoundException (but already checked),
      // RoleNotFoundException
      int currRoleRefNbr =
          (getRoleCardinality(relationId, currRoleName)).intValue();

      // Retrieves new number of element in role
      int currRoleNewRefNbr = currRoleRefNbr - 1;

      // Retrieves role info for that role
      //
      // Shall not throw RelationTypeNotFoundException or
      // RoleInfoNotFoundException
      RoleInfo currRoleInfo;
      try {
        currRoleInfo = getRoleInfo(currRelTypeName,
            currRoleName);
      } catch (RelationTypeNotFoundException exc1) {
        throw new RuntimeException(exc1.getMessage());
      } catch (RoleInfoNotFoundException exc2) {
        throw new RuntimeException(exc2.getMessage());
      }

      // Checks with expected minimum number of elements
      boolean chkMinFlag = currRoleInfo.checkMinDegree(currRoleNewRefNbr);

      if (!chkMinFlag) {
        // The relation has to be deleted
        deleteRelFlag = true;
      }
    }

    if (deleteRelFlag) {
      // Removes the relation
      removeRelation(relationId);

    } else {

      // Updates each role in the relation using
      // handleMBeanUnregistration() callback
      //
      // BEWARE: this roleNameList list MUST BE A COPY of a role name
      //         list for a referenced MBean in a relation, NOT a
      //         reference to an original one part of the
      //         myRefedMBeanObjName2RelIdsMap!!!! Because each role
      //         which name is in that list will be updated (potentially
      //         using setRole(). So the Relation Service will update the
      //         myRefedMBeanObjName2RelIdsMap to refelect the new role
      //         value!
      for (String currRoleName : roleNameList) {

        if (relObj instanceof RelationSupport) {
          // Internal relation
          // Can throw RoleNotFoundException (but already checked)
          //
          // Shall not throw
          // RelationTypeNotFoundException,
          // InvalidRoleValueException (value was correct, removing
          // one reference shall not invalidate it, else detected
          // above)
          try {
            ((RelationSupport) relObj).handleMBeanUnregistrationInt(
                objectName,
                currRoleName,
                true,
                this);
          } catch (RelationTypeNotFoundException exc3) {
            throw new RuntimeException(exc3.getMessage());
          } catch (InvalidRoleValueException exc4) {
            throw new RuntimeException(exc4.getMessage());
          }

        } else {
          // Relation MBean
          Object[] params = new Object[2];
          params[0] = objectName;
          params[1] = currRoleName;
          String[] signature = new String[2];
          signature[0] = "javax.management.ObjectName";
          signature[1] = "java.lang.String";
          // Shall not throw InstanceNotFoundException, or
          // MBeanException (wrapping RoleNotFoundException or
          // MBeanException or InvalidRoleValueException) or
          // ReflectionException
          try {
            myMBeanServer.invoke(((ObjectName) relObj),
                "handleMBeanUnregistration",
                params,
                signature);
          } catch (InstanceNotFoundException exc1) {
            throw new RuntimeException(exc1.getMessage());
          } catch (ReflectionException exc3) {
            throw new RuntimeException(exc3.getMessage());
          } catch (MBeanException exc2) {
            Exception wrappedExc = exc2.getTargetException();
            throw new RuntimeException(wrappedExc.getMessage());
          }

        }
      }
    }

    RELATION_LOGGER.exiting(RelationService.class.getName(),
        "handleReferenceUnregistration");
    return;
  }
}
